From da2100ded4bee62ab39b9a986dafebc09155db39 Mon Sep 17 00:00:00 2001 From: Sujith Date: Tue, 1 Nov 2022 12:51:42 +0530 Subject: [PATCH 01/71] feat: initial commit fulfillment base Signed-off-by: Sujith --- packages/api-plugin-fulfillment/.gitignore | 61 +++ packages/api-plugin-fulfillment/LICENSE | 201 +++++++++ packages/api-plugin-fulfillment/README.md | 68 +++ .../api-plugin-fulfillment/babel.config.cjs | 1 + packages/api-plugin-fulfillment/index.js | 3 + .../api-plugin-fulfillment/jest.config.cjs | 1 + packages/api-plugin-fulfillment/package.json | 49 +++ packages/api-plugin-fulfillment/src/index.js | 59 +++ .../createFulfillmentMethod.test.js.snap | 3 + .../src/mutations/createFulfillmentMethod.js | 59 +++ .../mutations/createFulfillmentMethod.test.js | 72 +++ .../src/mutations/createFulfillmentType.js | 38 ++ .../src/mutations/index.js | 15 + .../selectFulfillmentOptionForGroup.js | 57 +++ .../src/mutations/updateFulfillmentMethod.js | 69 +++ .../updateFulfillmentOptionsForGroup.js | 102 +++++ .../src/mutations/updateFulfillmentType.js | 55 +++ .../api-plugin-fulfillment/src/preStartup.js | 21 + .../src/queries/getFulfillmentMethods.js | 47 ++ .../getFulfillmentMethodsWithQuotes.js | 50 +++ .../src/queries/getFulfillmentType.js | 29 ++ .../src/queries/getFulfillmentTypes.js | 21 + .../src/queries/index.js | 12 + .../src/registration.js | 27 ++ .../src/resolvers/FulfillmentMethod/index.js | 5 + .../src/resolvers/Mutation/index.js | 11 + .../selectFulfillmentOptionForGroup.js | 43 ++ .../Mutation/updateFulfillmentMethod.js | 35 ++ .../updateFulfillmentOptionsForGroup.js | 32 ++ .../Mutation/updateFulfillmentType.js | 34 ++ .../resolvers/Query/getFulfillmentMethods.js | 32 ++ .../src/resolvers/Query/getFulfillmentType.js | 28 ++ .../resolvers/Query/getFulfillmentTypes.js | 31 ++ .../src/resolvers/Query/index.js | 9 + .../src/resolvers/index.js | 19 + .../src/schemas/index.js | 5 + .../src/schemas/schema.graphql | 414 ++++++++++++++++++ .../src/simpleSchemas.js | 133 ++++++ .../api-plugin-fulfillment/src/startup.js | 30 ++ .../src/tests/factory.js | 20 + .../src/util/extendCommonOrder.js | 93 ++++ .../src/util/getCartById.js | 34 ++ .../api-plugin-fulfillment/src/xforms/id.js | 18 + pnpm-lock.yaml | 185 +++++++- 44 files changed, 2327 insertions(+), 4 deletions(-) create mode 100644 packages/api-plugin-fulfillment/.gitignore create mode 100644 packages/api-plugin-fulfillment/LICENSE create mode 100644 packages/api-plugin-fulfillment/README.md create mode 100644 packages/api-plugin-fulfillment/babel.config.cjs create mode 100644 packages/api-plugin-fulfillment/index.js create mode 100644 packages/api-plugin-fulfillment/jest.config.cjs create mode 100644 packages/api-plugin-fulfillment/package.json create mode 100644 packages/api-plugin-fulfillment/src/index.js create mode 100644 packages/api-plugin-fulfillment/src/mutations/__snapshots__/createFulfillmentMethod.test.js.snap create mode 100644 packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.js create mode 100644 packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.test.js create mode 100644 packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.js create mode 100644 packages/api-plugin-fulfillment/src/mutations/index.js create mode 100644 packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.js create mode 100644 packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.js create mode 100644 packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.js create mode 100644 packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.js create mode 100644 packages/api-plugin-fulfillment/src/preStartup.js create mode 100644 packages/api-plugin-fulfillment/src/queries/getFulfillmentMethods.js create mode 100644 packages/api-plugin-fulfillment/src/queries/getFulfillmentMethodsWithQuotes.js create mode 100644 packages/api-plugin-fulfillment/src/queries/getFulfillmentType.js create mode 100644 packages/api-plugin-fulfillment/src/queries/getFulfillmentTypes.js create mode 100644 packages/api-plugin-fulfillment/src/queries/index.js create mode 100644 packages/api-plugin-fulfillment/src/registration.js create mode 100644 packages/api-plugin-fulfillment/src/resolvers/FulfillmentMethod/index.js create mode 100644 packages/api-plugin-fulfillment/src/resolvers/Mutation/index.js create mode 100644 packages/api-plugin-fulfillment/src/resolvers/Mutation/selectFulfillmentOptionForGroup.js create mode 100644 packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentMethod.js create mode 100644 packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentOptionsForGroup.js create mode 100644 packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentType.js create mode 100644 packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentMethods.js create mode 100644 packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentType.js create mode 100644 packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentTypes.js create mode 100644 packages/api-plugin-fulfillment/src/resolvers/Query/index.js create mode 100644 packages/api-plugin-fulfillment/src/resolvers/index.js create mode 100644 packages/api-plugin-fulfillment/src/schemas/index.js create mode 100644 packages/api-plugin-fulfillment/src/schemas/schema.graphql create mode 100644 packages/api-plugin-fulfillment/src/simpleSchemas.js create mode 100644 packages/api-plugin-fulfillment/src/startup.js create mode 100644 packages/api-plugin-fulfillment/src/tests/factory.js create mode 100644 packages/api-plugin-fulfillment/src/util/extendCommonOrder.js create mode 100644 packages/api-plugin-fulfillment/src/util/getCartById.js create mode 100644 packages/api-plugin-fulfillment/src/xforms/id.js diff --git a/packages/api-plugin-fulfillment/.gitignore b/packages/api-plugin-fulfillment/.gitignore new file mode 100644 index 00000000000..ad46b30886f --- /dev/null +++ b/packages/api-plugin-fulfillment/.gitignore @@ -0,0 +1,61 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# next.js build output +.next diff --git a/packages/api-plugin-fulfillment/LICENSE b/packages/api-plugin-fulfillment/LICENSE new file mode 100644 index 00000000000..261eeb9e9f8 --- /dev/null +++ b/packages/api-plugin-fulfillment/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/api-plugin-fulfillment/README.md b/packages/api-plugin-fulfillment/README.md new file mode 100644 index 00000000000..53aaf9acead --- /dev/null +++ b/packages/api-plugin-fulfillment/README.md @@ -0,0 +1,68 @@ +# api-plugin-fulfillment + +[![npm (scoped)](https://img.shields.io/npm/v/@reactioncommerce/api-plugin-fulfillment.svg)](https://www.npmjs.com/package/@reactioncommerce/api-plugin-fulfillment) +[![CircleCI](https://circleci.com/gh/reactioncommerce/api-plugin-fulfillment.svg?style=svg)](https://circleci.com/gh/reactioncommerce/api-plugin-fulfillment) + + +## Summary + +This plugin provides the base for all fulfillment types and the fulfillment methods under each type. + +## Included in this fulfillment plugin +### `src/` + +The `src` folder is where all the plugin files resides. + +### `.gitignore` + +A basic `gitignore` file +### `babel.config.cjs` + +If your plugin includes linting and tests, this file is required to allow esmodules to run correctly. + +### `jest.config.cjs` + +If your plugin includes tests, this file is required to allow esmodules to run correctly. You'll need to update the `transformIgnorePatterns` and `moduleNameMapper` sections to include any esmodule `npm` packages used in your plugin. + +### `License.md` + +If your plugin uses `Apache 2` licensing, you can leave this file as-is. If another type of licensing is used, you need to update this file, and the README, accordingly. + +### `package.json` + +The provided `package.json` is set up to install all needed packages and config for linting, testing, and semantic-release. You'll need to update the `name`, `description`, and add any new dependencies your plugin files use. + +### `index.js` + +The entrypoint file for your npm package, will most likely just export your plugin registration from the `src` folder. + +## Developer Certificate of Origin +We use the [Developer Certificate of Origin (DCO)](https://developercertificate.org/) in lieu of a Contributor License Agreement for all contributions to Reaction Commerce open source projects. We request that contributors agree to the terms of the DCO and indicate that agreement by signing all commits made to Reaction Commerce projects by adding a line with your name and email address to every Git commit message contributed: +``` +Signed-off-by: Jane Doe +``` + +You can sign your commit automatically with Git by using `git commit -s` if you have your `user.name` and `user.email` set as part of your Git configuration. + +We ask that you use your real name (please no anonymous contributions or pseudonyms). By signing your commit you are certifying that you have the right have the right to submit it under the open source license used by that particular Reaction Commerce project. You must use your real name (no pseudonyms or anonymous contributions are allowed.) + +We use the [Probot DCO GitHub app](https://github.com/apps/dco) to check for DCO signoffs of every commit. + +If you forget to sign your commits, the DCO bot will remind you and give you detailed instructions for how to amend your commits to add a signature. + +## License + + Copyright 2020 Reaction Commerce + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/packages/api-plugin-fulfillment/babel.config.cjs b/packages/api-plugin-fulfillment/babel.config.cjs new file mode 100644 index 00000000000..5fa924c0809 --- /dev/null +++ b/packages/api-plugin-fulfillment/babel.config.cjs @@ -0,0 +1 @@ +module.exports = require("@reactioncommerce/api-utils/lib/configs/babel.config.cjs"); diff --git a/packages/api-plugin-fulfillment/index.js b/packages/api-plugin-fulfillment/index.js new file mode 100644 index 00000000000..d7ea8b28c59 --- /dev/null +++ b/packages/api-plugin-fulfillment/index.js @@ -0,0 +1,3 @@ +import register from "./src/index.js"; + +export default register; diff --git a/packages/api-plugin-fulfillment/jest.config.cjs b/packages/api-plugin-fulfillment/jest.config.cjs new file mode 100644 index 00000000000..2bdefefceb9 --- /dev/null +++ b/packages/api-plugin-fulfillment/jest.config.cjs @@ -0,0 +1 @@ +module.exports = require("@reactioncommerce/api-utils/lib/configs/jest.config.cjs"); diff --git a/packages/api-plugin-fulfillment/package.json b/packages/api-plugin-fulfillment/package.json new file mode 100644 index 00000000000..5497612a419 --- /dev/null +++ b/packages/api-plugin-fulfillment/package.json @@ -0,0 +1,49 @@ +{ + "name": "@reactioncommerce/api-plugin-fulfillment", + "description": "Base Fulfillment plugin for implementing other fulfillment type plugins and their corresponding fulfillment method plugins", + "label": "Fulfillment Plugin", + "version": "1.0.0", + "main": "index.js", + "type": "module", + "engines": { + "node": ">=14.18.1", + "npm": ">=7" + }, + "homepage": "https://github.com/reactioncommerce/reaction", + "url": "https://github.com/reactioncommerce/reaction.git", + "email": "hello-open-commerce@mailchimp.com", + "repository": { + "type": "git", + "url": "https://github.com/reactioncommerce/reaction.git", + "directory": "packages/api-plugin-fulfillment" + }, + "author": { + "name": "Mailchimp Open Commerce", + "email": "hello-open-commerce@mailchimp.com", + "url": "https://mailchimp.com/developer/open-commerce/" + }, + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/reactioncommerce/api-plugin-fulfillment/issues" + }, + "sideEffects": false, + "dependencies": { + "@reactioncommerce/logger": "^1.1.3", + "@reactioncommerce/api-utils": "^1.16.9", + "@reactioncommerce/reaction-error": "^1.0.1", + "@reactioncommerce/random": "^1.0.2", + "lodash": "^4.17.21", + "simpl-schema": "^1.12.2" + }, + "devDependencies": { + "@reactioncommerce/api-plugin-carts": "^1.0.0", + "@reactioncommerce/data-factory": "~1.0.1" + }, + "scripts": { + "lint": "npm run lint:eslint", + "lint:eslint": "eslint .", + "test": "jest", + "test:watch": "jest --watch", + "test:file": "jest --no-cache --watch --coverage=false" + } +} diff --git a/packages/api-plugin-fulfillment/src/index.js b/packages/api-plugin-fulfillment/src/index.js new file mode 100644 index 00000000000..2a006b76fac --- /dev/null +++ b/packages/api-plugin-fulfillment/src/index.js @@ -0,0 +1,59 @@ +import { createRequire } from "module"; +import mutations from "./mutations/index.js"; +import queries from "./queries/index.js"; +import resolvers from "./resolvers/index.js"; +import schemas from "./schemas/index.js"; +import startup from "./startup.js"; +import preStartup from "./preStartup.js"; +import { MethodEmptyData } from "./simpleSchemas.js"; +import { allRegisteredFulfillmentTypes, registerPluginHandlerForFulfillmentTypes } from "./registration.js"; + +const require = createRequire(import.meta.url); +const pkg = require("../package.json"); + +/** + * @summary Import and call this function to add this plugin to your API. + * @param {Object} app The ReactionAPI instance + * @returns {undefined} + */ +export default async function register(app) { + await app.registerPlugin({ + label: "Fulfillment", + name: "fulfillment", + version: pkg.version, + collections: { + FulfillmentRestrictions: { + name: "FulfillmentRestrictions", + indexes: [ + [{ methodIds: 1 }] + ] + }, + Fulfillment: { + name: "Fulfillment", + indexes: [ + // Create indexes. We set specific names for backwards compatibility + // with indexes created by the aldeed:schema-index Meteor package. + [{ name: 1 }, { name: "c2_name" }], + [{ shopId: 1 }, { name: "c2_shopId" }] + ] + } + }, + graphQL: { + schemas, + resolvers + }, + queries, + mutations, + functionsByType: { + registerPluginHandler: [registerPluginHandlerForFulfillmentTypes], + preStartup: [preStartup], + startup: [startup] + }, + contextAdditions: { + allRegisteredFulfillmentTypes + }, + simpleSchemas: { + MethodEmptyData + } + }); +} diff --git a/packages/api-plugin-fulfillment/src/mutations/__snapshots__/createFulfillmentMethod.test.js.snap b/packages/api-plugin-fulfillment/src/mutations/__snapshots__/createFulfillmentMethod.test.js.snap new file mode 100644 index 00000000000..79e2bc2c52b --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/__snapshots__/createFulfillmentMethod.test.js.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`throws if required fields are not supplied 1`] = `"Fulfillment types is required"`; diff --git a/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.js b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.js new file mode 100644 index 00000000000..b0985d03a74 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.js @@ -0,0 +1,59 @@ +import SimpleSchema from "simpl-schema"; +import Random from "@reactioncommerce/random"; +import ReactionError from "@reactioncommerce/reaction-error"; +import { FulfillmentMethodSchema } from "../simpleSchemas.js"; + +const inputSchema = new SimpleSchema({ + method: FulfillmentMethodSchema, + fulfillmentTypeId: String, + shopId: String +}); + +/** + * @method createFulfillmentMethodMutation + * @summary Creates a fulfillment method + * @param {Object} context - an object containing the per-request state + * @param {Object} input - Input (see SimpleSchema) + * @returns {Promise} An object with a `method` property containing the created method + */ +export default async function createFulfillmentMethodMutation(context, input) { + const cleanedInput = inputSchema.clean(input); // add default values and such + inputSchema.validate(cleanedInput); + + const { method: inputMethod, fulfillmentTypeId, shopId } = cleanedInput; + const { collections } = context; + const { Fulfillment } = collections; + const method = { ...inputMethod }; + + await context.validatePermissions("reaction:legacy:fulfillmentMethods", "create", { shopId }); + + const ffTypeRecord = await Fulfillment.findOne({ _id: fulfillmentTypeId, shopId }); + if (!ffTypeRecord) throw new ReactionError("server-error", "Unable to create fulfillment method without defined type"); + + const ffTypeMethodRecord = await Fulfillment.findOne({ + _id: fulfillmentTypeId, + shopId, + methods: { $elemMatch: { name: method.name, fulfillmentMethod: method.fulfillmentMethod } } + }); + if (ffTypeMethodRecord) throw new ReactionError("server-error", "Fulfillment Method already exists"); + + method._id = Random.id(); + // MongoDB schema still uses `enabled` rather than `isEnabled` + // method.enabled = method.isEnabled; + // delete method.isEnabled; + + method.fulfillmentTypes = [ffTypeRecord.fulfillmentType]; + + const { matchedCount } = await Fulfillment.updateOne({ + shopId, + _id: fulfillmentTypeId + }, { + $addToSet: { + methods: method + } + }); + + if (matchedCount === 0) throw new ReactionError("server-error", "Unable to create fulfillment method"); + + return { group: method }; +} diff --git a/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.test.js b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.test.js new file mode 100644 index 00000000000..9248b986a28 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.test.js @@ -0,0 +1,72 @@ +import mockCollection from "@reactioncommerce/api-utils/tests/mockCollection.js"; +import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; +import createFulfillmentMethodMutation from "./createFulfillmentMethod.js"; + +mockContext.validatePermissions = jest.fn().mockName("validatePermissions"); +mockContext.collections.Fulfillment = mockCollection("Fulfillment"); +mockContext.validatePermissions.mockReturnValueOnce(Promise.resolve(null)); + +test("throws if required fields are not supplied", async () => { + const fulfillmentMethodInput = { + method: { + shopId: "SHOP_ID", + cost: 99, + fulfillmentMethod: "flatRates", + displayMessageMethod: "Sample display message" + }, + fulfillmentTypeId: "fulfillment123", + shopId: "SHOP_ID" + }; + + await expect(createFulfillmentMethodMutation(mockContext, fulfillmentMethodInput)).rejects.toThrowErrorMatchingSnapshot(); +}); + +test("add a new fulfillment method", async () => { + mockContext.collections.Fulfillment.updateOne.mockReturnValueOnce(Promise.resolve({ result: { ok: 1 } })); + mockContext.collections.Fulfillment.findOne.mockReturnValueOnce(Promise.resolve({ + _id: "fulfillment123", + name: "Default Shipping Provider", + shopId: "SHOP_ID", + provider: { + enabled: true, + label: "Shipping", + name: "shipping" + }, + fulfillmentType: "shipping" + })); + + + const result = await createFulfillmentMethodMutation(mockContext, { + method: { + shopId: "SHOP_ID", + cost: 99, + handling: 99, + rate: 99, + fulfillmentTypes: ["shipping"], + group: "Ground", + enabled: true, + label: "Flat Rate", + name: "flatRates", + fulfillmentMethod: "flatRates", + displayMessageMethod: "Sample display message" + }, + fulfillmentTypeId: "fulfillment123", + shopId: "SHOP_ID" + }); + + expect(result).toEqual({ + group: { + _id: expect.any(String), + cost: 99, + handling: 99, + rate: 99, + fulfillmentTypes: ["shipping"], + group: "Ground", + enabled: true, + label: "Flat Rate", + name: "flatRates", + fulfillmentMethod: "flatRates", + displayMessageMethod: "Sample display message" + } + }); +}); diff --git a/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.js b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.js new file mode 100644 index 00000000000..bd2bcadfcfa --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.js @@ -0,0 +1,38 @@ +import Random from "@reactioncommerce/random"; +import ReactionError from "@reactioncommerce/reaction-error"; +import { fulfillmentTypeSchema } from "../simpleSchemas.js"; + +/** + * @method createFulfillmentType + * @summary updates the selected fulfillment type + * @param {Object} context - an object containing the per-request state + * @param {Object} input - Input (see SimpleSchema) + * @returns {Promise} An object with the updated type + */ +export default async function createFulfillmentType(context, input) { + const cleanedInput = fulfillmentTypeSchema.clean(input); + cleanedInput.provider.name = cleanedInput.name; + + if (cleanedInput.method) { + cleanedInput.method._id = Random.id(); + cleanedInput.method.fulfillmentTypes = [cleanedInput.fulfillmentType]; + } + fulfillmentTypeSchema.validate(cleanedInput); + + const { collections } = context; + const { Fulfillment } = collections; + const { shopId, name, fulfillmentType } = cleanedInput; + + const ffTypeRecord = await Fulfillment.findOne({ name, shopId, fulfillmentType }); + if (ffTypeRecord) throw new ReactionError("invalid-parameter", "Fulfillment Type already exists"); + + await context.validatePermissions("reaction:legacy:fulfillmentTypes", "create", { shopId }); + + const { insertedCount } = await Fulfillment.insertOne({ + _id: Random.id(), + ...cleanedInput + }); + if (insertedCount === 0) throw new ReactionError("server-error", "Unable to create fulfillment type"); + + return { group: { name: cleanedInput.name, fulfillmentType: cleanedInput.fulfillmentType } }; +} diff --git a/packages/api-plugin-fulfillment/src/mutations/index.js b/packages/api-plugin-fulfillment/src/mutations/index.js new file mode 100644 index 00000000000..53b79e27d40 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/index.js @@ -0,0 +1,15 @@ +import selectFulfillmentOptionForGroup from "./selectFulfillmentOptionForGroup.js"; +import updateFulfillmentOptionsForGroup from "./updateFulfillmentOptionsForGroup.js"; +import updateFulfillmentType from "./updateFulfillmentType.js"; +import createFulfillmentType from "./createFulfillmentType.js"; +import updateFulfillmentMethod from "./updateFulfillmentMethod.js"; +import createFulfillmentMethod from "./createFulfillmentMethod.js"; + +export default { + createFulfillmentType, + updateFulfillmentType, + createFulfillmentMethod, + updateFulfillmentMethod, + selectFulfillmentOptionForGroup, + updateFulfillmentOptionsForGroup +}; diff --git a/packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.js b/packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.js new file mode 100644 index 00000000000..f6a56024e21 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.js @@ -0,0 +1,57 @@ +import SimpleSchema from "simpl-schema"; +import ReactionError from "@reactioncommerce/reaction-error"; +import getCartById from "../util/getCartById.js"; + +const inputSchema = new SimpleSchema({ + cartId: String, + cartToken: { + type: String, + optional: true + }, + fulfillmentGroupId: String, + fulfillmentMethodId: String +}); + +/** + * @method selectFulfillmentOptionForGroup + * @summary Selects a fulfillment option for a fulfillment group + * @param {Object} context - an object containing the per-request state + * @param {Object} input - an object of all mutation arguments that were sent by the client + * @param {String} input.cartId - The ID of the cart to select a fulfillment option for + * @param {String} [input.cartToken] - The token for the cart, required if it is an anonymous cart + * @param {String} input.fulfillmentGroupId - The group to select a fulfillment option for + * @param {String} input.fulfillmentMethodId - The fulfillment method ID from the option the shopper selected + * @returns {Promise} An object with a `cart` property containing the updated cart + */ +export default async function selectFulfillmentOptionForGroup(context, input) { + const cleanedInput = inputSchema.clean(input || {}); + + inputSchema.validate(cleanedInput); + + const { cartId, cartToken, fulfillmentGroupId, fulfillmentMethodId } = cleanedInput; + + const cart = await getCartById(context, cartId, { cartToken, throwIfNotFound: true }); + + const fulfillmentGroup = (cart.shipping || []).find((group) => group._id === fulfillmentGroupId); + if (!fulfillmentGroup) throw new ReactionError("not-found", `Fulfillment group with ID ${fulfillmentGroupId} not found in cart with ID ${cartId}`); + + // Make sure there is an option for this group that has the requested ID + const option = (fulfillmentGroup.shipmentQuotes || []).find((quote) => quote.method._id === fulfillmentMethodId); + if (!option) throw new ReactionError("not-found", `Fulfillment option with method ID ${fulfillmentMethodId} not found in cart with ID ${cartId}`); + + const updatedCart = { + ...cart, + shipping: cart.shipping.map((group) => { + if (group._id === fulfillmentGroupId) { + return { ...group, shipmentMethod: option.method }; + } + + return group; + }), + updatedAt: new Date() + }; + + const savedCart = await context.mutations.saveCart(context, updatedCart); + + return { cart: savedCart }; +} diff --git a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.js b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.js new file mode 100644 index 00000000000..152c7a215f1 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.js @@ -0,0 +1,69 @@ +import SimpleSchema from "simpl-schema"; +import ReactionError from "@reactioncommerce/reaction-error"; +import { FulfillmentMethodSchema } from "../simpleSchemas.js"; + +const inputSchema = new SimpleSchema({ + method: FulfillmentMethodSchema, + fulfillmentTypeId: String, + methodId: String, + shopId: String +}); + +/** + * @method updateFulfillmentMethodMutation + * @summary updates a flat rate fulfillment method + * @param {Object} context - an object containing the per-request state + * @param {Object} input - Input (see SimpleSchema) + * @returns {Promise} An object with a `method` property containing the updated method + */ +export default async function updateFulfillmentMethodMutation(context, input) { + const cleanedInput = inputSchema.clean(input); // add default values and such + inputSchema.validate(cleanedInput); + + const { method: inputMethod, methodId, fulfillmentTypeId, shopId } = cleanedInput; + const { collections } = context; + const { Fulfillment } = collections; + const method = { ...inputMethod }; + + if (!fulfillmentTypeId) throw new ReactionError("invalid-parameter", "Fulfillment Type ID to be updated not provided"); + if (!methodId) throw new ReactionError("invalid-parameter", "Method ID to be updated not provided"); + + await context.validatePermissions(`reaction:legacy:fulfillmentMethods:${methodId}`, "update", { shopId }); + + // MongoDB schema still uses `enabled` rather than `isEnabled` + // method.enabled = method.isEnabled; + // delete method.isEnabled; + + const ffTypeMethodRecord = await Fulfillment.findOne({ + "_id": fulfillmentTypeId, + shopId, + "methods._id": methodId + }); + if (!ffTypeMethodRecord) throw new ReactionError("server-error", "Fulfillment Method does not exist"); + + // Do not update the fulfillmentType, fulfillmentMethod, group, name & _id fields + // Find the matching fulfillmentMethod object and use those values to over-write + const currentFulfillmentMethod = (ffTypeMethodRecord.methods || []).find((meth) => meth._id === methodId); + if (!currentFulfillmentMethod) throw new ReactionError("server-error", "Fulfillment Method does not exist"); + const updatedMethod = { + ...method, + _id: methodId, + name: currentFulfillmentMethod.name, + group: currentFulfillmentMethod.group, + fulfillmentMethod: currentFulfillmentMethod.fulfillmentMethod, + fulfillmentType: [ffTypeMethodRecord.fulfillmentType] + }; + + const { matchedCount } = await Fulfillment.updateOne({ + "_id": fulfillmentTypeId, + "methods._id": methodId, + shopId + }, { + $set: { + "methods.$": updatedMethod + } + }); + if (matchedCount === 0) throw new ReactionError("not-found", "Fulfillment type to be updated not found"); + + return { group: updatedMethod }; +} diff --git a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.js b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.js new file mode 100644 index 00000000000..ada20aa6637 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.js @@ -0,0 +1,102 @@ +import _ from "lodash"; +import SimpleSchema from "simpl-schema"; +import ReactionError from "@reactioncommerce/reaction-error"; +import getCartById from "../util/getCartById.js"; + +const inputSchema = new SimpleSchema({ + cartId: String, + cartToken: { + type: String, + optional: true + }, + fulfillmentGroupId: String +}); + +/** + * @name getShipmentQuotesQueryStatus + * @param {Array} rates Rate array + * @returns {Object} An object with `shipmentQuotes` and `shipmentQuotesQueryStatus` on it + * @private + */ +function getShipmentQuotesQueryStatus(rates) { + if (rates.length === 0) { + return { + shipmentQuotes: [], + shipmentQuotesQueryStatus: { + requestStatus: "pending" + } + }; + } + + const errorResult = rates.find((option) => option.requestStatus === "error"); + if (errorResult) { + return { + shipmentQuotes: [], + shipmentQuotesQueryStatus: { + requestStatus: errorResult.requestStatus, + shippingProvider: errorResult.shippingProvider, + message: errorResult.message + } + }; + } + + return { + shipmentQuotes: rates, + shipmentQuotesQueryStatus: { + requestStatus: "success", + numOfShippingMethodsFound: rates.length + } + }; +} + +/** + * @method updateFulfillmentOptionsForGroup + * @summary Updates the fulfillment quotes for a fulfillment group + * @param {Object} context - an object containing the per-request state + * @param {Object} input - an object of all mutation arguments that were sent by the client + * @param {String} input.cartId - The ID of the cart to update fulfillment options for + * @param {String} [input.cartToken] - The token for the cart, required if it is an anonymous cart + * @param {String} input.fulfillmentGroupId - The group to update fulfillment options for + * @returns {Promise} An object with a `cart` property containing the updated cart + */ +export default async function updateFulfillmentOptionsForGroup(context, input) { + const cleanedInput = inputSchema.clean(input || {}); + inputSchema.validate(cleanedInput); + + const { cartId, cartToken, fulfillmentGroupId } = cleanedInput; + + const cart = await getCartById(context, cartId, { cartToken, throwIfNotFound: true }); + + // This is done by `saveCart`, too, but we need to do it before every call to `getCommonOrderForCartGroup` + // to avoid errors in the case where a product has been deleted since the last time this cart was saved. + // This mutates that `cart` object. + await context.mutations.removeMissingItemsFromCart(context, cart); + + const fulfillmentGroup = (cart.shipping || []).find((group) => group._id === fulfillmentGroupId); + if (!fulfillmentGroup) throw new ReactionError("not-found", `Fulfillment group with ID ${fulfillmentGroupId} not found in cart with ID ${cartId}`); + + const commonOrder = await context.queries.getCommonOrderForCartGroup(context, { cart, fulfillmentGroupId: fulfillmentGroup._id }); + // In the future we want to do this async and subscribe to the results + const rates = await context.queries.getFulfillmentMethodsWithQuotes(commonOrder, context); + + const { shipmentQuotes, shipmentQuotesQueryStatus } = getShipmentQuotesQueryStatus(rates); + + if (!_.isEqual(shipmentQuotes, fulfillmentGroup.shipmentQuotes) || !_.isEqual(shipmentQuotesQueryStatus, fulfillmentGroup.shipmentQuotesQueryStatus)) { + const updatedCart = { + ...cart, + shipping: cart.shipping.map((group) => { + if (group._id === fulfillmentGroupId) { + return { ...group, shipmentQuotes, shipmentQuotesQueryStatus }; + } + + return group; + }), + updatedAt: new Date() + }; + + const savedCart = await context.mutations.saveCart(context, updatedCart); + + return { cart: savedCart }; + } + return { cart }; +} diff --git a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.js b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.js new file mode 100644 index 00000000000..089c91e0b4c --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.js @@ -0,0 +1,55 @@ +import SimpleSchema from "simpl-schema"; +import ReactionError from "@reactioncommerce/reaction-error"; + +const inputSchema = new SimpleSchema({ + fulfillmentGroupId: String, + shopId: String, + name: String, + enabled: { + type: Boolean, + defaultValue: true + }, + label: String, + displayMessageType: { + type: String, + optional: true + } +}); + +/** + * @method updateFulfillmentType + * @summary updates the selected fulfillment type + * @param {Object} context - an object containing the per-request state + * @param {Object} input - Input (see SimpleSchema) + * @returns {Promise} An object with the updated type + */ +export default async function updateFulfillmentType(context, input) { + const cleanedInput = inputSchema.clean(input); // add default values and such + inputSchema.validate(cleanedInput); + + const { fulfillmentGroupId, shopId, name, enabled, label, displayMessageType } = cleanedInput; + const { collections } = context; + const { Fulfillment } = collections; + + if (!shopId) throw new ReactionError("invalid-parameter", "Shop ID to be updated not provided"); + if (!fulfillmentGroupId) throw new ReactionError("invalid-parameter", "FulfillmentType ID to be updated not provided"); + if (!name) throw new ReactionError("invalid-parameter", "FulfillmentType Name to be updated not provided"); + + await context.validatePermissions(`reaction:legacy:fulfillmentTypes:${fulfillmentGroupId}`, "update", { shopId }); + + const { matchedCount } = await Fulfillment.updateOne({ + _id: fulfillmentGroupId, + shopId + }, { + $set: { + name, + "provider.enabled": enabled, + "provider.name": name, + "provider.label": label, + displayMessageType + } + }); + if (matchedCount === 0) throw new ReactionError("not-found", "Fulfillment type to update not found"); + + return { group: cleanedInput }; +} diff --git a/packages/api-plugin-fulfillment/src/preStartup.js b/packages/api-plugin-fulfillment/src/preStartup.js new file mode 100644 index 00000000000..a2f7d44ccf5 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/preStartup.js @@ -0,0 +1,21 @@ +import Logger from "@reactioncommerce/logger"; +import ReactionError from "@reactioncommerce/reaction-error"; +import { extendFulfillmentSchemas } from "./simpleSchemas.js"; + +const logCtx = { name: "fulfillment", file: "preStartup" }; + +/** + * @summary Called before startup to extend schemas + * @param {Object} context Startup context + * @returns {undefined} + */ +export default async function fulfillmentPreStartup(context) { + const allFulfillmentTypesArray = context.allRegisteredFulfillmentTypes?.registeredFulfillmentTypes; + + if (!allFulfillmentTypesArray || allFulfillmentTypesArray.length === 0) { + Logger.warn(logCtx, "No fulfillment types available"); + throw new ReactionError("not-configured", "No fulfillment types available"); + } + + extendFulfillmentSchemas(context.simpleSchemas, allFulfillmentTypesArray); +} diff --git a/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethods.js b/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethods.js new file mode 100644 index 00000000000..3731ea54832 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethods.js @@ -0,0 +1,47 @@ +/** + * @name getFulfillmentMethods + * @method + * @memberof Fulfillment/Queries + * @summary Query the Fulfillment collection for a list of fulfillment Methods + * @param {Object} context - an object containing the per-request state + * @param {Object} input - Request input + * @param {String} input.shopId - The shop id of the fulfillment types + * @param {String} input.fulfillmentTypeId - The fulfillmentType id of the fulfillment type + * @returns {Promise} Mongo cursor + */ +export default async function getFulfillmentMethods(context, input) { + const { collections } = context; + const { Fulfillment } = collections; + const { shopId, fulfillmentTypeId } = input; + + await context.validatePermissions("reaction:legacy:fulfillmentTypes", "read", { shopId }); + + // aggregate pipeline to extract fulfillment methods inside Fulfillment collection + return { + collection: Fulfillment, + pipeline: [ + { + $match: { + shopId, + _id: fulfillmentTypeId + } + }, + { + $unwind: "$methods" + }, + { + $replaceRoot: { + newRoot: { + $mergeObjects: [ + "$methods", + { + fulfillmentTypeId: "$$ROOT._id", + shopId: "$$ROOT.shopId" + } + ] + } + } + } + ] + }; +} diff --git a/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethodsWithQuotes.js b/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethodsWithQuotes.js new file mode 100644 index 00000000000..02b1428e6c2 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethodsWithQuotes.js @@ -0,0 +1,50 @@ +import Logger from "@reactioncommerce/logger"; +import ReactionError from "@reactioncommerce/reaction-error"; +import extendCommonOrder from "../util/extendCommonOrder.js"; + +const logCtx = { name: "fulfillment", file: "getFulfillmentWithQuotes" }; + +/** + * @name getFulfillmentMethodsWithQuotes + * @method + * @summary Just gets rates, without updating anything + * @param {Object} commonOrder - details about the purchase a user wants to make. + * @param {Object} context - Context + * @returns {Array} return updated rates in cart + * @private + */ +export default async function getFulfillmentMethodsWithQuotes(commonOrder, context) { + const rates = []; + const retrialTargets = []; + + // must have items to calculate shipping + if (!commonOrder.items || !commonOrder.items.length) { + Logger.debug(logCtx, "getFulfillmentMethodsWithQuotes called with CommonOrder with no items"); + return rates; + } + const commonOrderExtended = await extendCommonOrder(context, commonOrder); + + const fulfillmentTypeInGroup = commonOrder.fulfillmentType; + if (!fulfillmentTypeInGroup) throw new ReactionError("not-found", "Fulfillment type not found in commonOrder"); + + const functionTypesToCall = `getFulfillmentMethodsWithQuotes${fulfillmentTypeInGroup}`; + const funcs = context.getFunctionsOfType(functionTypesToCall); + + if (!funcs || !Array.isArray(funcs) || !funcs.length) throw new ReactionError("not-found", `No methods for Fulfillment type ${fulfillmentTypeInGroup}`); + + let promises = funcs.map((rateFunction) => rateFunction(context, commonOrderExtended, [rates, retrialTargets])); + await Promise.all(promises); + + // Try once more. + if (retrialTargets.length > 0) { + promises = funcs.map((rateFunction) => rateFunction(context, commonOrderExtended, [rates, retrialTargets])); + await Promise.all(promises); + + if (retrialTargets.length > 0) { + Logger.warn({ ...logCtx, retrialTargets }, "Failed to get fulfillment methods from these packages:"); + } + } + + Logger.debug({ ...logCtx, rates }, "getFulfillmentMethodsWithQuotes returning rates"); + return rates; +} diff --git a/packages/api-plugin-fulfillment/src/queries/getFulfillmentType.js b/packages/api-plugin-fulfillment/src/queries/getFulfillmentType.js new file mode 100644 index 00000000000..c2b0596efc2 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/queries/getFulfillmentType.js @@ -0,0 +1,29 @@ +/** + * @name getFulfillmentType + * @method + * @memberof Fulfillment/Queries + * @summary Query the Fulfillment collection for a single fulfillment type + * @param {Object} context - an object containing the per-request state + * @param {Object} input - Request input + * @param {String} input.fulfillmentTypeId - The fulfillment type id + * @param {String} input.shopId - The shop id of the fulfillment type + * @returns {Promise} Mongo cursor + */ +export default async function getFulfillmentType(context, input) { + const { collections } = context; + const { Fulfillment } = collections; + const { fulfillmentTypeId, shopId } = input; + + await context.validatePermissions("reaction:legacy:fulfillmentTypes", "read", { shopId }); + + const doc = await Fulfillment.findOne({ + _id: fulfillmentTypeId, + shopId + }); + if (!doc) return null; + + return { + ...doc, + shopId + }; +} diff --git a/packages/api-plugin-fulfillment/src/queries/getFulfillmentTypes.js b/packages/api-plugin-fulfillment/src/queries/getFulfillmentTypes.js new file mode 100644 index 00000000000..546d01d672f --- /dev/null +++ b/packages/api-plugin-fulfillment/src/queries/getFulfillmentTypes.js @@ -0,0 +1,21 @@ +/** + * @name getFulfillmentTypes + * @method + * @memberof Fulfillment/Queries + * @summary Query the Fulfillment collection for a list of fulfillment types + * @param {Object} context - an object containing the per-request state + * @param {Object} input - Request input + * @param {String} input.shopId - The shop id of the fulfillment types + * @returns {Promise} Mongo cursor + */ +export default async function getFulfillmentTypes(context, input) { + const { collections } = context; + const { Fulfillment } = collections; + const { shopId } = input; + + await context.validatePermissions("reaction:legacy:fulfillmentTypes", "read", { shopId }); + + return Fulfillment.find({ + shopId + }); +} diff --git a/packages/api-plugin-fulfillment/src/queries/index.js b/packages/api-plugin-fulfillment/src/queries/index.js new file mode 100644 index 00000000000..3582994cb92 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/queries/index.js @@ -0,0 +1,12 @@ +// import allFulfillmentTypes from "./allFulfillmentTypes.js"; +import getFulfillmentMethodsWithQuotes from "./getFulfillmentMethodsWithQuotes.js"; +import getFulfillmentType from "./getFulfillmentType.js"; +import getFulfillmentTypes from "./getFulfillmentTypes.js"; +import getFulfillmentMethods from "./getFulfillmentMethods.js"; + +export default { + getFulfillmentType, + getFulfillmentTypes, + getFulfillmentMethods, + getFulfillmentMethodsWithQuotes +}; diff --git a/packages/api-plugin-fulfillment/src/registration.js b/packages/api-plugin-fulfillment/src/registration.js new file mode 100644 index 00000000000..9645f27e9eb --- /dev/null +++ b/packages/api-plugin-fulfillment/src/registration.js @@ -0,0 +1,27 @@ +import SimpleSchema from "simpl-schema"; + +const FulfillmentTypeDeclaration = new SimpleSchema({ + "registeredFulfillmentTypes": { + type: Array + }, + "registeredFulfillmentTypes.$": { + type: String + } +}); + +export const allRegisteredFulfillmentTypes = { + registeredFulfillmentTypes: ["undecided"] +}; + +/** + * @summary Collect all the registered Fulfillment types + * @param {Object} registeredFulfillmentTypes - Fulfillment types passed in via child plugins + * @returns {undefined} undefined + */ +export function registerPluginHandlerForFulfillmentTypes({ registeredFulfillmentTypes }) { + if (registeredFulfillmentTypes) { + allRegisteredFulfillmentTypes.registeredFulfillmentTypes = allRegisteredFulfillmentTypes.registeredFulfillmentTypes.concat(registeredFulfillmentTypes); + } + + FulfillmentTypeDeclaration.validate(allRegisteredFulfillmentTypes); +} diff --git a/packages/api-plugin-fulfillment/src/resolvers/FulfillmentMethod/index.js b/packages/api-plugin-fulfillment/src/resolvers/FulfillmentMethod/index.js new file mode 100644 index 00000000000..1a84788268b --- /dev/null +++ b/packages/api-plugin-fulfillment/src/resolvers/FulfillmentMethod/index.js @@ -0,0 +1,5 @@ +import { encodeFulfillmentMethodOpaqueId } from "../../xforms/id.js"; + +export default { + _id: (node) => encodeFulfillmentMethodOpaqueId(node._id) +}; diff --git a/packages/api-plugin-fulfillment/src/resolvers/Mutation/index.js b/packages/api-plugin-fulfillment/src/resolvers/Mutation/index.js new file mode 100644 index 00000000000..6ae592f0426 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/resolvers/Mutation/index.js @@ -0,0 +1,11 @@ +import selectFulfillmentOptionForGroup from "./selectFulfillmentOptionForGroup.js"; +import updateFulfillmentOptionsForGroup from "./updateFulfillmentOptionsForGroup.js"; +import updateFulfillmentType from "./updateFulfillmentType.js"; +import updateFulfillmentMethod from "./updateFulfillmentMethod.js"; + +export default { + updateFulfillmentMethod, + updateFulfillmentType, + selectFulfillmentOptionForGroup, + updateFulfillmentOptionsForGroup +}; diff --git a/packages/api-plugin-fulfillment/src/resolvers/Mutation/selectFulfillmentOptionForGroup.js b/packages/api-plugin-fulfillment/src/resolvers/Mutation/selectFulfillmentOptionForGroup.js new file mode 100644 index 00000000000..260efda3b50 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/resolvers/Mutation/selectFulfillmentOptionForGroup.js @@ -0,0 +1,43 @@ +import { decodeCartOpaqueId, decodeFulfillmentGroupOpaqueId, decodeFulfillmentMethodOpaqueId } from "../../xforms/id.js"; +import selectFulfillmentOptionForGroupMutation from "../../mutations/selectFulfillmentOptionForGroup.js"; + +/** + * @name Mutation/selectFulfillmentOptionForGroup + * @method + * @memberof Cart/GraphQL + * @summary resolver for the selectFulfillmentOptionForGroup GraphQL mutation + * @param {Object} parentResult - unused + * @param {Object} args.input - an object of all mutation arguments that were sent by the client + * @param {String} args.input.cartId - The ID of the cart to select a fulfillment option for + * @param {String} [args.input.cartToken] - The token for the cart, required if it is an anonymous cart + * @param {String} args.input.fulfillmentGroupId - The group to select a fulfillment option for + * @param {String} args.input.fulfillmentMethodId - The fulfillment method ID from the option the shopper selected + * @param {String} [args.input.clientMutationId] - An optional string identifying the mutation call + * @param {Object} context - an object containing the per-request state + * @returns {Promise} SelectFulfillmentOptionForGroupPayload + */ +export default async function selectFulfillmentOptionForGroup(parentResult, { input }, context) { + const { + cartId: opaqueCartId, + cartToken, + clientMutationId = null, + fulfillmentGroupId: opaqueFulfillmentGroupId, + fulfillmentMethodId: opaqueFulfillmentMethodId + } = input; + + const cartId = decodeCartOpaqueId(opaqueCartId); + const fulfillmentGroupId = decodeFulfillmentGroupOpaqueId(opaqueFulfillmentGroupId); + const fulfillmentMethodId = decodeFulfillmentMethodOpaqueId(opaqueFulfillmentMethodId); + + const { cart } = await selectFulfillmentOptionForGroupMutation(context, { + cartId, + cartToken, + fulfillmentGroupId, + fulfillmentMethodId + }); + + return { + cart, + clientMutationId + }; +} diff --git a/packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentMethod.js b/packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentMethod.js new file mode 100644 index 00000000000..d99e72074f0 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentMethod.js @@ -0,0 +1,35 @@ +import { decodeShopOpaqueId, decodeFulfillmentGroupOpaqueId, decodeFulfillmentMethodOpaqueId } from "../../xforms/id.js"; +import updateFulfillmentMethodMutation from "../../mutations/updateFulfillmentMethod.js"; +/** + * @name Mutation/updateFulfillmentMethod + * @method + * @memberof Cart/GraphQL + * @summary resolver for the updateFulfillmentMethod GraphQL mutation + * @param {Object} parentResult - unused + * @param {Object} args.input - an object of all mutation arguments that were sent by the client + * @param {String} args.input.fulfillmentTypeId - The fulfillment type to be updated + * @param {String} args.input.methodId - The fulfillment method to be updated + * @param {String} args.input.shopId - The ShopId to which the fulfillment group belongs + * @param {String} args.input.method - The fulfillment method data to be updated + * @param {String} [args.input.clientMutationId] - An optional string identifying the mutation call + * @param {Object} context - an object containing the per-request state + * @returns {Promise} updateFulfillmentMethodPayload + */ +export default async function updateFulfillmentMethod(parentResult, { input }, context) { + const { shopId: opaqueShopId, clientMutationId = null, fulfillmentTypeId: opaqueFulfillmentTypeId, methodId: opaqueMethodId, method } = input.groupInfo; + + const methodId = decodeFulfillmentMethodOpaqueId(opaqueMethodId); + const fulfillmentTypeId = decodeFulfillmentGroupOpaqueId(opaqueFulfillmentTypeId); + const shopId = decodeShopOpaqueId(opaqueShopId); + const { group } = await updateFulfillmentMethodMutation(context, { + shopId, + fulfillmentTypeId, + methodId, + method + }); + + return { + group, + clientMutationId + }; +} diff --git a/packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentOptionsForGroup.js b/packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentOptionsForGroup.js new file mode 100644 index 00000000000..0045b0dee80 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentOptionsForGroup.js @@ -0,0 +1,32 @@ +import { decodeCartOpaqueId, decodeFulfillmentGroupOpaqueId } from "../../xforms/id.js"; +import updateFulfillmentOptionsForGroupMutation from "../../mutations/updateFulfillmentOptionsForGroup.js"; +/** + * @name Mutation/updateFulfillmentOptionsForGroup + * @method + * @memberof Cart/GraphQL + * @summary resolver for the updateFulfillmentOptionsForGroup GraphQL mutation + * @param {Object} parentResult - unused + * @param {Object} args.input - an object of all mutation arguments that were sent by the client + * @param {String} args.input.cartId - The ID of the cart to update fulfillment options for + * @param {String} [args.input.cartToken] - The token for the cart, required if it is an anonymous cart + * @param {String} args.input.fulfillmentGroupId - The group to update fulfillment options for + * @param {String} [args.input.clientMutationId] - An optional string identifying the mutation call + * @param {Object} context - an object containing the per-request state + * @returns {Promise} UpdateFulfillmentOptionsForGroupPayload + */ +export default async function updateFulfillmentOptionsForGroup(parentResult, { input }, context) { + const { cartId: opaqueCartId, cartToken, clientMutationId = null, fulfillmentGroupId: opaqueFulfillmentGroupId } = input; + + const fulfillmentGroupId = decodeFulfillmentGroupOpaqueId(opaqueFulfillmentGroupId); + const cartId = decodeCartOpaqueId(opaqueCartId); + const { cart } = await updateFulfillmentOptionsForGroupMutation(context, { + cartId, + cartToken, + fulfillmentGroupId + }); + + return { + cart, + clientMutationId + }; +} diff --git a/packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentType.js b/packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentType.js new file mode 100644 index 00000000000..a19ead70dd8 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentType.js @@ -0,0 +1,34 @@ +import { decodeShopOpaqueId, decodeFulfillmentGroupOpaqueId } from "../../xforms/id.js"; +import updateFulfillmentTypeMutation from "../../mutations/updateFulfillmentType.js"; +/** + * @name Mutation/updateFulfillmentType + * @method + * @memberof Cart/GraphQL + * @summary resolver for the updateFulfillmentType GraphQL mutation + * @param {Object} parentResult - unused + * @param {Object} args.input - an object of all mutation arguments that were sent by the client + * @param {String} args.input.fulfillmentGroupId - The fulfillment group to be updated + * @param {String} args.input.shopId - The ShopId to which the fulfillment group belongs + * @param {String} args.input.name - The fulfillment group name to be updated + * @param {String} args.input.enabled - Flag to enable/disable group + * @param {String} [args.input.clientMutationId] - An optional string identifying the mutation call + * @param {Object} context - an object containing the per-request state + * @returns {Promise} updateFulfillmentTypePayload + */ +export default async function updateFulfillmentType(parentResult, { input }, context) { + const { groupInfo, clientMutationId = null } = input; + const { shopId: opaqueShopId, fulfillmentGroupId: opaqueFulfillmentGroupId } = groupInfo; + + const fulfillmentGroupId = decodeFulfillmentGroupOpaqueId(opaqueFulfillmentGroupId); + const shopId = decodeShopOpaqueId(opaqueShopId); + const { group } = await updateFulfillmentTypeMutation(context, { + ...groupInfo, + shopId, + fulfillmentGroupId + }); + + return { + group, + clientMutationId + }; +} diff --git a/packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentMethods.js b/packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentMethods.js new file mode 100644 index 00000000000..6ca46d0a053 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentMethods.js @@ -0,0 +1,32 @@ +import getPaginatedResponseFromAggregate from "@reactioncommerce/api-utils/graphql/getPaginatedResponseFromAggregate.js"; +import wasFieldRequested from "@reactioncommerce/api-utils/graphql/wasFieldRequested.js"; +import { decodeShopOpaqueId, decodeFulfillmentGroupOpaqueId } from "../../xforms/id.js"; + +/** + * @name Query/getFulfillmentMethods + * @method + * @memberof Fulfillment/Query + * @summary Query for a list of fulfillment methods + * @param {Object} _ - unused + * @param {Object} args - an object of all arguments that were sent by the client + * @param {String} args.shopId - Shop ID to get records for + * @param {Object} context - an object containing the per-request state + * @param {Object} info Info about the GraphQL request + * @returns {Promise} Fulfillment methods + */ +export default async function getFulfillmentMethods(_, args, context, info) { + const { shopId: opaqueShopId, fulfillmentTypeId: opaqueFulfillmentId, ...connectionArgs } = args; + + const shopId = decodeShopOpaqueId(opaqueShopId); + const fulfillmentTypeId = decodeFulfillmentGroupOpaqueId(opaqueFulfillmentId); + + const { collection, pipeline } = await context.queries.getFulfillmentMethods(context, { + shopId, fulfillmentTypeId + }); + + return getPaginatedResponseFromAggregate(collection, pipeline, connectionArgs, { + includeHasNextPage: wasFieldRequested("pageInfo.hasNextPage", info), + includeHasPreviousPage: wasFieldRequested("pageInfo.hasPreviousPage", info), + includeTotalCount: wasFieldRequested("totalCount", info) + }); +} diff --git a/packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentType.js b/packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentType.js new file mode 100644 index 00000000000..e1c9bd0f022 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentType.js @@ -0,0 +1,28 @@ +import { decodeFulfillmentGroupOpaqueId, decodeShopOpaqueId } from "../../xforms/id.js"; + +/** + * @name Query/getFulfillmentType + * @method + * @memberof Fulfillment/Query + * @summary Query for a single fulfillment type + * @param {Object} _ - unused + * @param {Object} args - an object of all arguments that were sent by the client + * @param {String} args.fulfillmentTypeId - Fulfillment type ID to get the record of + * @param {String} args.shopId - Shop ID to get record for + * @param {Object} context - an object containing the per-request state + * @returns {Promise} Fulfillment type + */ +export default async function getFulfillmentType(_, args, context) { + const { + fulfillmentTypeId: opaqueTypeId, + shopId: opaqueShopId + } = args; + + const fulfillmentTypeId = decodeFulfillmentGroupOpaqueId(opaqueTypeId); + const shopId = decodeShopOpaqueId(opaqueShopId); + + return context.queries.getFulfillmentType(context, { + fulfillmentTypeId, + shopId + }); +} diff --git a/packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentTypes.js b/packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentTypes.js new file mode 100644 index 00000000000..f37eb83c2db --- /dev/null +++ b/packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentTypes.js @@ -0,0 +1,31 @@ +import getPaginatedResponse from "@reactioncommerce/api-utils/graphql/getPaginatedResponse.js"; +import wasFieldRequested from "@reactioncommerce/api-utils/graphql/wasFieldRequested.js"; +import { decodeShopOpaqueId } from "../../xforms/id.js"; + +/** + * @name Query/FulfillmenTypes + * @method + * @memberof Fulfillment/Query + * @summary Query for a list of fulfillment types + * @param {Object} _ - unused + * @param {Object} args - an object of all arguments that were sent by the client + * @param {String} args.shopId - Shop ID to get records for + * @param {Object} context - an object containing the per-request state + * @param {Object} info Info about the GraphQL request + * @returns {Promise} Fulfillment methods + */ +export default async function getFulfillmentTypes(_, args, context, info) { + const { shopId: opaqueShopId, ...connectionArgs } = args; + + const shopId = decodeShopOpaqueId(opaqueShopId); + + const cursor = await context.queries.getFulfillmentTypes(context, { + shopId + }); + + return getPaginatedResponse(cursor, connectionArgs, { + includeHasNextPage: wasFieldRequested("pageInfo.hasNextPage", info), + includeHasPreviousPage: wasFieldRequested("pageInfo.hasPreviousPage", info), + includeTotalCount: wasFieldRequested("totalCount", info) + }); +} diff --git a/packages/api-plugin-fulfillment/src/resolvers/Query/index.js b/packages/api-plugin-fulfillment/src/resolvers/Query/index.js new file mode 100644 index 00000000000..fa4e54c3eda --- /dev/null +++ b/packages/api-plugin-fulfillment/src/resolvers/Query/index.js @@ -0,0 +1,9 @@ +import getFulfillmentType from "./getFulfillmentType.js"; +import getFulfillmentTypes from "./getFulfillmentTypes.js"; +import getFulfillmentMethods from "./getFulfillmentMethods.js"; + +export default { + getFulfillmentType, + getFulfillmentTypes, + getFulfillmentMethods +}; diff --git a/packages/api-plugin-fulfillment/src/resolvers/index.js b/packages/api-plugin-fulfillment/src/resolvers/index.js new file mode 100644 index 00000000000..63b65a3443d --- /dev/null +++ b/packages/api-plugin-fulfillment/src/resolvers/index.js @@ -0,0 +1,19 @@ +import FulfillmentMethod from "./FulfillmentMethod/index.js"; +import Mutation from "./Mutation/index.js"; +import Query from "./Query/index.js"; + +/** + * Fulfillment related GraphQL resolvers + * @namespace Fulfillment/GraphQL + */ + +export default { + FulfillmentMethod, + Mutation, + Query, + AdditionalData: { + __resolveType(obj) { + return obj.gqlType; + } + } +}; diff --git a/packages/api-plugin-fulfillment/src/schemas/index.js b/packages/api-plugin-fulfillment/src/schemas/index.js new file mode 100644 index 00000000000..30096f92e54 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/schemas/index.js @@ -0,0 +1,5 @@ +import importAsString from "@reactioncommerce/api-utils/importAsString.js"; + +const schema = importAsString("./schema.graphql"); + +export default [schema]; diff --git a/packages/api-plugin-fulfillment/src/schemas/schema.graphql b/packages/api-plugin-fulfillment/src/schemas/schema.graphql new file mode 100644 index 00000000000..5a7369daa09 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/schemas/schema.graphql @@ -0,0 +1,414 @@ +"Allowed fulfillment types, extended by fulfillment-type plugins" +enum FulfillmentType { + "Default fulfillment type when none is decided by user" + undecided +} + +"Default empty object for additionalData" +type emptyData { + gqlType: String + emptyData: Boolean +} + +"Union of Additional Data fields" +union AdditionalData = emptyData + +""" +A single fulfillment method. Fulfillment methods are shown to shoppers along with a quote for them, +and the shopper chooses one method per fulfillment group per cart during checkout. +""" +type FulfillmentMethod implements Node { + "The fulfillment method ID" + _id: ID! + + "A carrier name" + carrier: String + + "The name of this method, for display in the user interface" + displayName: String! + + """ + The fulfillment types for which this method may be used. For example, `shipping` or `digital`. + """ + fulfillmentTypes: [FulfillmentType]! + + "The group to which this method belongs" + group: String + + "The name of this method, a unique identifier" + name: String! + + "Additional data provided by this method" + methodAdditionalData: AdditionalData +} + + +"Defines a fulfillment method in general." +type FulfillmentMethodObj { + "The fulfillment method ID" + _id: ID! + + "The cost of this fulfillment method to the shop, if you track this" + cost: Float + + """ + The fulfillment types for which this method may be used. For example, `shipping` or `digital`. + """ + fulfillmentTypes: [FulfillmentType] + + "The group to which this method belongs" + group: String! + + "A fixed price to charge for handling costs when this fulfillment method is selected for an order" + handling: Float! + + "Include this as a fulfillment option shown to shoppers during checkout?" + enabled: Boolean! + + "The name of this method, for display in the user interface" + label: String! + + "The name of this method, a unique identifier" + name: String! + + "The common-name for this method, to group all variants of the same method" + fulfillmentMethod: String + + "A fixed price to charge for fulfillment costs when this fulfillment method is selected for an order" + rate: Float! +} + +"Provider info" +type ProviderInfo{ + "Name of the provider" + name: String! + + "Label of the provider" + label: String! + + "Flag defining enabled/disabled status" + enabled: Boolean +} +"Fulfillment type root object" +type FulfillmentTypeObj implements Node { + "The Fulfillment Type ID" + _id: ID! + + "The user provided name of the fulfillment type" + name: String! + + "Shop ID" + shopId: ID! + + "ProviderInfo" + provider: ProviderInfo + + "Fulfillment type" + fulfillmentType: String + + "Fulfillment methods" + methods: [FulfillmentMethodObj] +} + +"Custom Fulfillment method object " +type CustomFulfillmentMethodObj implements Node { + "The Fulfillment Type ID" + fulfillmentTypeId: ID! + + "Shop ID" + shopId: ID! + + "The fulfillment method ID" + _id: ID! + + "The cost of this fulfillment method to the shop, if you track this" + cost: Float + + """ + The fulfillment types for which this method may be used. For example, `shipping` or `digital`. + """ + fulfillmentTypes: [FulfillmentType] + + "The group to which this method belongs" + group: String! + + "A fixed price to charge for handling costs when this fulfillment method is selected for an order" + handling: Float! + + "Include this as a fulfillment option shown to shoppers during checkout?" + enabled: Boolean! + + "The name of this method, for display in the user interface" + label: String! + + "The name of this method, a unique identifier" + name: String! + + "The common-name for this method, to group all variants of the same method" + fulfillmentMethod: String + + "A fixed price to charge for fulfillment costs when this fulfillment method is selected for an order" + rate: Float! +} + +"Input needed to select a fulfillment option for a single fulfillment group on a cart" +input updateFulfillmentTypeInputInfo { + "The shop to which this group belongs" + shopId: ID! + + "The group to update" + fulfillmentGroupId: ID! + + "The type name to be updated" + name: String! + + "The label to be updated" + label: String! + + "The displayMessage to be updated" + displayMessageType: String + + "Flag to define if the group should be enabled/disabled" + enabled: Boolean +} +"Input needed to select a fulfillment option for a single fulfillment group on a cart" +input updateFulfillmentTypeInput { + "The shop to which this group belongs" + groupInfo: updateFulfillmentTypeInputInfo! + + "An optional string identifying the mutation call, which will be returned in the response payload" + clientMutationId: String +} +"The updated group info from updateFulfillmentType" +type updateFulfillmentTypeGroup { + "The shop to which this group belongs" + shopId: ID! + + "The group which was updated" + fulfillmentGroupId: ID! + + "The updated group name" + name: String! + + "The updated group label" + label: String! + + "Flag defining enabled/disabled status" + enabled: Boolean +} +"The response from the `updateFulfillmentType` mutation" +type updateFulfillmentTypePayload { + "The updated Group" + group: updateFulfillmentTypeGroup! + + "The same string you sent with the mutation params, for matching mutation calls with their responses" + clientMutationId: String +} + + + +"Method info input" +input MethodInfoInput{ + "Name of the provider" + name: String! + + "Label of the provider" + label: String! + + "Fulfillmenttypes" + fulfillmentTypes: [String]! + + "Group" + group: String! + + "Display message Method" + displayMessageMethod: String + + "Cost" + cost: Int + + "Handling" + handling: Int! + + "Rate" + rate: Int! + + "Flag defining enabled/disabled status" + enabled: Boolean! + + "FulfillmentMethod" + fulfillmentMethod: String +} + + +"The updated group infofrom updateFulfillmentMethod" +input updateFulfillmentMethodInfo { + "Shop Id" + shopId: String! + + "The group which was updated" + fulfillmentTypeId: String! + + "The method which has to be updated" + methodId: String! + + "Method info" + method: MethodInfoInput +} +input updateFulfillmentMethodInput { + "Group Info Fulfillment Method" + groupInfo: updateFulfillmentMethodInfo! + + "An optional string identifying the mutation call, which will be returned in the response payload" + clientMutationId: String +} +type updateFulfillmentMethodPayload { + "The inserted Group" + group: FulfillmentMethodObj! + + "The same string you sent with the mutation params, for matching mutation calls with their responses" + clientMutationId: String +} + +extend type Mutation { + "Updates the Name and Enabled fields for the provided Fulfillment Type" + updateFulfillmentType( + "Mutation input" + input: updateFulfillmentTypeInput! + ): updateFulfillmentTypePayload! + + "Updates the provided Fulfillment Method" + updateFulfillmentMethod( + "Mutation input" + input: updateFulfillmentMethodInput! + ): updateFulfillmentMethodPayload! +} + +extend type Query { + "Get a fulfillment type" + getFulfillmentType( + "Fulfillment type id" + fulfillmentTypeId: ID! + + "Shop ID" + shopId: ID! + ): FulfillmentTypeObj! + + "Get all fulfillment types" + getFulfillmentTypes( + "Shop ID" + shopId: ID! + + "Return only results that come after this cursor. Use this with `first` to specify the number of results to return." + after: ConnectionCursor, + + "Return only results that come before this cursor. Use this with `last` to specify the number of results to return." + before: ConnectionCursor, + + "Return at most this many results. This parameter may be used with either `after` or `offset` parameters." + first: ConnectionLimitInt, + + "Return at most this many results. This parameter may be used with the `before` parameter." + last: ConnectionLimitInt, + + "Return only results that come after the Nth result. This parameter may be used with the `first` parameter." + offset: Int, + ): FulfillmentTypeObjConnection! + + "Get all fulfillment methods for the given type" + getFulfillmentMethods( + "Shop ID" + shopId: ID! + + "Fulfillment Type ID" + fulfillmentTypeId: ID! + + "Return only results that come after this cursor. Use this with `first` to specify the number of results to return." + after: ConnectionCursor, + + "Return only results that come before this cursor. Use this with `last` to specify the number of results to return." + before: ConnectionCursor, + + "Return at most this many results. This parameter may be used with either `after` or `offset` parameters." + first: ConnectionLimitInt, + + "Return at most this many results. This parameter may be used with the `before` parameter." + last: ConnectionLimitInt, + + "Return only results that come after the Nth result. This parameter may be used with the `first` parameter." + offset: Int, + ): FulfillmentMethodObjConnection! +} + + +#### +# Connections +#### + +"A connection edge in which each node is a `FulfillmentTypeObj` object" +type FulfillmentTypeObjEdge { + "The cursor that represents this node in the paginated results" + cursor: ConnectionCursor! + + "The fulfillment method" + node: FulfillmentTypeObj +} + +"A connection edge in which each node is a `CustomFulfillmentMethodObj` object" +type CustomFulfillmentMethodObjEdge { + "The cursor that represents this node in the paginated results" + cursor: ConnectionCursor! + + "The fulfillment method" + node: CustomFulfillmentMethodObj +} + +""" +Wraps a list of FulfillmentTypes, providing pagination cursors and information. + +For information about what Relay-compatible connections are and how to use them, see the following articles: +- [Relay Connection Documentation](https://facebook.github.io/relay/docs/en/graphql-server-specification.html#connections) +- [Relay Connection Specification](https://facebook.github.io/relay/graphql/connections.htm) +- [Using Relay-style Connections With Apollo Client](https://www.apollographql.com/docs/react/recipes/pagination.html) +""" +type FulfillmentTypeObjConnection { + "The list of nodes that match the query, wrapped in an edge to provide a cursor string for each" + edges: [FulfillmentTypeObjEdge] + + """ + You can request the `nodes` directly to avoid the extra wrapping that `NodeEdge` has, + if you know you will not need to paginate the results. + """ + nodes: [FulfillmentTypeObj] + + "Information to help a client request the next or previous page" + pageInfo: PageInfo! + + "The total number of nodes that match your query" + totalCount: Int! +} + +""" +Wraps a list of Cusom FulfillmentMethods, providing pagination cursors and information. + +For information about what Relay-compatible connections are and how to use them, see the following articles: +- [Relay Connection Documentation](https://facebook.github.io/relay/docs/en/graphql-server-specification.html#connections) +- [Relay Connection Specification](https://facebook.github.io/relay/graphql/connections.htm) +- [Using Relay-style Connections With Apollo Client](https://www.apollographql.com/docs/react/recipes/pagination.html) +""" +type FulfillmentMethodObjConnection { + "The list of nodes that match the query, wrapped in an edge to provide a cursor string for each" + edges: [CustomFulfillmentMethodObjEdge] + + """ + You can request the `nodes` directly to avoid the extra wrapping that `NodeEdge` has, + if you know you will not need to paginate the results. + """ + nodes: [CustomFulfillmentMethodObj] + + "Information to help a client request the next or previous page" + pageInfo: PageInfo! + + "The total number of nodes that match your query" + totalCount: Int! +} diff --git a/packages/api-plugin-fulfillment/src/simpleSchemas.js b/packages/api-plugin-fulfillment/src/simpleSchemas.js new file mode 100644 index 00000000000..8ecd68298cd --- /dev/null +++ b/packages/api-plugin-fulfillment/src/simpleSchemas.js @@ -0,0 +1,133 @@ +import SimpleSchema from "simpl-schema"; + +/** + * @name MethodEmptyData + * @memberof Schemas + * @type {SimpleSchema} + * @summary Defines Empty placeholder + * @property {String} gqlType Defines the method type + * @property {Boolean} emptyData Empty Data fields + */ +export const MethodEmptyData = new SimpleSchema({ + gqlType: String, + emptyData: { + type: Boolean, + optional: true + } +}); + + +export const FulfillmentMethodSchema = new SimpleSchema({ + "cost": { + type: Number, + optional: true + }, + "fulfillmentTypes": { + type: Array, + minCount: 1 + }, + "fulfillmentTypes.$": String, + "group": String, + "handling": Number, + "enabled": Boolean, + "label": String, + "name": String, + "fulfillmentMethod": { + type: String, + optional: true + }, + "displayMessageMethod": { + type: String, + optional: true + }, + "rate": Number +}); + +const ProviderSchema = new SimpleSchema({ + enabled: Boolean, + label: String, + name: String +}); + +export const fulfillmentTypeSchema = new SimpleSchema({ + "name": String, + "shopId": String, + "provider": { + type: ProviderSchema + }, + "fulfillmentType": String, + "displayMessageType": { + type: String, + optional: true + }, + "methods": { + type: Array, + optional: true + }, + "methods.$": { + type: FulfillmentMethodSchema + } +}); + +/** + * @summary Extend the schema with updated allowedValues + * @param {Object} schemas Schemas from context passed in + * @param {String[]} allFulfillmentTypesArray Array of all fulfillment types + * @returns {undefined} + */ +export function extendFulfillmentSchemas(schemas, allFulfillmentTypesArray) { + const schemaProductExtension = { + "supportedFulfillmentTypes.$": { + allowedValues: allFulfillmentTypesArray + } + }; + schemas.Product.extend(schemaProductExtension); + + const schemaCatalogProductExtension = { + "supportedFulfillmentTypes.$": { + allowedValues: allFulfillmentTypesArray + } + }; + schemas.CatalogProduct.extend(schemaCatalogProductExtension); + + const schemaShipmentExtension = { + type: { + allowedValues: allFulfillmentTypesArray + } + }; + schemas.Shipment.extend(schemaShipmentExtension); + + const schemaCartItemExtension = { + "selectedFulfillmentType": { + allowedValues: allFulfillmentTypesArray + }, + "supportedFulfillmentTypes.$": { + allowedValues: allFulfillmentTypesArray + } + }; + schemas.CartItem.extend(schemaCartItemExtension); + + const schemaExtension = { + type: { + allowedValues: allFulfillmentTypesArray + } + }; + const schemaExtensionCommonOrder = { + fulfillmentType: { + allowedValues: allFulfillmentTypesArray + } + }; + schemas.CommonOrder.extend(schemaExtensionCommonOrder); + schemas.orderFulfillmentGroupInputSchema.extend(schemaExtension); + schemas.OrderFulfillmentGroup.extend(schemaExtension); + + const schemaExtensionMethodAdditionalData = { + methodAdditionalData: { + type: SimpleSchema.oneOf(MethodEmptyData), + optional: true, + label: "Method specific additional data" + } + }; + schemas.SelectedFulfillmentOption.extend(schemaExtensionMethodAdditionalData); + schemas.ShippingMethod.extend(schemaExtensionMethodAdditionalData); +} diff --git a/packages/api-plugin-fulfillment/src/startup.js b/packages/api-plugin-fulfillment/src/startup.js new file mode 100644 index 00000000000..509d20ea20d --- /dev/null +++ b/packages/api-plugin-fulfillment/src/startup.js @@ -0,0 +1,30 @@ +/** + * @summary Called on startup to creates root entry for undecided fulfillment type + * @param {Object} context Startup context + * @param {Object} context.collections Map of MongoDB collections + * @returns {undefined} + */ +export default async function fulfillmentTypeUndecidedStartup(context) { + const { collections } = context; + const { Fulfillment } = collections; + + context.appEvents.on("afterShopCreate", async (payload) => { + const { shop } = payload; + const shopId = shop._id; + + const undecidedRecord = await Fulfillment.findOne({ fulfillmentType: "undecided", shopId }); + if (!undecidedRecord) { + const groupInfo = { + name: "Undecided Group", + shopId, + provider: { + enabled: true, + label: "Undecided", + name: "undecided" + }, + fulfillmentType: "undecided" + }; + await context.mutations.createFulfillmentType(context.getInternalContext(), groupInfo); + } + }); +} diff --git a/packages/api-plugin-fulfillment/src/tests/factory.js b/packages/api-plugin-fulfillment/src/tests/factory.js new file mode 100644 index 00000000000..427e1346b63 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/tests/factory.js @@ -0,0 +1,20 @@ +import { createFactoryForSchema, Factory } from "@reactioncommerce/data-factory"; + +import { + Cart, + ShipmentQuote +} from "@reactioncommerce/api-plugin-carts/src/simpleSchemas.js"; + +const schemasToAddToFactory = { + Cart, + ShipmentQuote +}; + +// Adds each to `Factory` object. For example, `Factory.Cart` +// will be the factory that builds an object that matches the +// `Cart` schema. +Object.keys(schemasToAddToFactory).forEach((key) => { + createFactoryForSchema(key, schemasToAddToFactory[key]); +}); + +export default Factory; diff --git a/packages/api-plugin-fulfillment/src/util/extendCommonOrder.js b/packages/api-plugin-fulfillment/src/util/extendCommonOrder.js new file mode 100644 index 00000000000..b9a019d408a --- /dev/null +++ b/packages/api-plugin-fulfillment/src/util/extendCommonOrder.js @@ -0,0 +1,93 @@ +import _ from "lodash"; +import ReactionError from "@reactioncommerce/reaction-error"; +import tagsForCatalogProducts from "@reactioncommerce/api-utils/tagsForCatalogProducts.js"; + +/** + * @name mergeProductAndVariants + * @summary Merges a product and its variants + * @param {Object} productAndVariants - The product and its variants + * @returns {Object} - The merged product and variants + */ +function mergeProductAndVariants(productAndVariants) { + const { product, parentVariant, variant } = productAndVariants; + + // Filter out unnecessary product props + const productProps = _.omit(product, [ + "variants", "media", "metafields", "parcel", " primaryImage", "socialMetadata", "customAttributes" + ]); + + // Filter out unnecessary variant props + const variantExcludeProps = ["media", "parcel", "primaryImage", "customAttributes"]; + const variantProps = _.omit(variant, variantExcludeProps); + + // If an option has been added to the cart + if (parentVariant) { + // Filter out unnecessary parent variant props + const parentVariantProps = _.omit(parentVariant, variantExcludeProps); + + return { + ...productProps, + ...parentVariantProps, + ...variantProps + }; + } + + return { + ...productProps, + ...variantProps + }; +} + +/** + * @name extendCommonOrder + * @summary Add extra properties to all CommonOrder items, which will be used to + * determine any applicable shipping restrictions. + * @param {Object} context - an object containing the per-request state + * @param {Object} commonOrder - details about the purchase a user wants to make. + * @returns {Object|null} The CommonOrder, with each item in `.items` extended. + */ +export default async function extendCommonOrder(context, commonOrder) { + const { collections: { Tags }, getFunctionsOfType, queries } = context; + const { items: orderItems } = commonOrder; + const extendedOrderItems = []; + + // Products in the Catalog collection are the source of truth, therefore use them + // as the source of data instead of what is coming from the client. + const catalogProductsAndVariants = await queries.findCatalogProductsAndVariants(context, orderItems); + const allProductsTags = await tagsForCatalogProducts(Tags, catalogProductsAndVariants); + + for (const orderLineItem of orderItems) { + const productAndVariants = catalogProductsAndVariants.find((catProduct) => catProduct.product.productId === orderLineItem.productId); + + if (!productAndVariants) { + throw new ReactionError("not-found", "Catalog product not found"); + } + + const extendedOrderItem = { + ...orderLineItem, + ...mergeProductAndVariants(productAndVariants) + }; + + // Fetch product tags + const tagInfo = allProductsTags.find((info) => info.productId === extendedOrderItem.productId); + if (tagInfo) { + extendedOrderItem.tags = tagInfo.tags; + } + + // Fetch custom attributes + // We need to run each of these functions in a series, rather than in parallel, because + // we are mutating the same object on each pass. It is recommended to disable `no-await-in-loop` + // eslint rules when the output of one iteration might be used as input in another iteration, such as this case here. + // See https://eslint.org/docs/rules/no-await-in-loop#when-not-to-use-it + for (const customAttributesFunc of getFunctionsOfType("addShippingRestrictionCustomAttributes")) { + await customAttributesFunc(extendedOrderItem, productAndVariants); // eslint-disable-line + } + + extendedOrderItems.push(extendedOrderItem); + } + + return { + ...commonOrder, + items: extendedOrderItems + }; +} diff --git a/packages/api-plugin-fulfillment/src/util/getCartById.js b/packages/api-plugin-fulfillment/src/util/getCartById.js new file mode 100644 index 00000000000..3142f9d4f69 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/util/getCartById.js @@ -0,0 +1,34 @@ +import hashToken from "@reactioncommerce/api-utils/hashToken.js"; +import ReactionError from "@reactioncommerce/reaction-error"; + +/** + * @summary Gets a cart from the db by ID. If there is an account for the request, verifies that the + * account has permission to access the cart. Optionally throws an error if not found. + * @param {Object} context Object defining the request state + * @param {String} cartId The cart ID + * @param {Object} [options] Options + * @param {String} [options.cartToken] Cart token, required if it's an anonymous cart + * @param {Boolean} [options.throwIfNotFound] Default false. Throw a not-found error rather than return null `cart` + * @returns {Object|null} The cart document, or null if not found and `throwIfNotFound` was false + */ +export default async function getCartById(context, cartId, { cartToken, throwIfNotFound = false } = {}) { + const { accountId, collections } = context; + const { Cart } = collections; + + const selector = { _id: cartId }; + + if (cartToken) { + selector.anonymousAccessToken = hashToken(cartToken); + } + + const cart = await Cart.findOne(selector); + if (!cart && throwIfNotFound) { + throw new ReactionError("not-found", "Cart not found"); + } + + if (cart && cart.accountId && cart.accountId !== accountId) { + throw new ReactionError("access-denied", "Access Denied"); + } + + return cart || null; +} diff --git a/packages/api-plugin-fulfillment/src/xforms/id.js b/packages/api-plugin-fulfillment/src/xforms/id.js new file mode 100644 index 00000000000..5697bf0747d --- /dev/null +++ b/packages/api-plugin-fulfillment/src/xforms/id.js @@ -0,0 +1,18 @@ +import decodeOpaqueIdForNamespace from "@reactioncommerce/api-utils/decodeOpaqueIdForNamespace.js"; +import encodeOpaqueId from "@reactioncommerce/api-utils/encodeOpaqueId.js"; + +const namespaces = { + Shop: "reaction/shop", + Cart: "reaction/cart", + FulfillmentGroup: "reaction/fulfillmentGroup", + FulfillmentMethod: "reaction/fulfillmentMethod" +}; + +export const encodeShopOpaqueId = encodeOpaqueId(namespaces.Shop); +export const encodeFulfillmentMethodOpaqueId = encodeOpaqueId(namespaces.FulfillmentMethod); +export const encodeFulfillmentGroupOpaqueId = encodeOpaqueId(namespaces.FulfillmentGroup); + +export const decodeShopOpaqueId = decodeOpaqueIdForNamespace(namespaces.Shop); +export const decodeCartOpaqueId = decodeOpaqueIdForNamespace(namespaces.Cart); +export const decodeFulfillmentGroupOpaqueId = decodeOpaqueIdForNamespace(namespaces.FulfillmentGroup); +export const decodeFulfillmentMethodOpaqueId = decodeOpaqueIdForNamespace(namespaces.FulfillmentMethod); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fdc9d5f9920..1055843771e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -225,16 +225,16 @@ importers: '@reactioncommerce/api-plugin-payments-example': link:../../packages/api-plugin-payments-example '@reactioncommerce/api-plugin-payments-stripe-sca': link:../../packages/api-plugin-payments-stripe-sca '@reactioncommerce/api-plugin-pricing-simple': link:../../packages/api-plugin-pricing-simple - '@reactioncommerce/api-plugin-products': link:../../packages/api-plugin-products + '@reactioncommerce/api-plugin-products': 1.3.0_graphql@14.7.0 '@reactioncommerce/api-plugin-settings': link:../../packages/api-plugin-settings '@reactioncommerce/api-plugin-shipments': link:../../packages/api-plugin-shipments '@reactioncommerce/api-plugin-shipments-flat-rate': link:../../packages/api-plugin-shipments-flat-rate '@reactioncommerce/api-plugin-shops': link:../../packages/api-plugin-shops '@reactioncommerce/api-plugin-simple-schema': link:../../packages/api-plugin-simple-schema '@reactioncommerce/api-plugin-sitemap-generator': link:../../packages/api-plugin-sitemap-generator - '@reactioncommerce/api-plugin-surcharges': link:../../packages/api-plugin-surcharges + '@reactioncommerce/api-plugin-surcharges': 1.1.7_graphql@14.7.0 '@reactioncommerce/api-plugin-system-information': link:../../packages/api-plugin-system-information - '@reactioncommerce/api-plugin-tags': link:../../packages/api-plugin-tags + '@reactioncommerce/api-plugin-tags': 1.2.0_graphql@14.7.0 '@reactioncommerce/api-plugin-taxes': link:../../packages/api-plugin-taxes '@reactioncommerce/api-plugin-taxes-flat-rate': link:../../packages/api-plugin-taxes-flat-rate '@reactioncommerce/api-plugin-translations': link:../../packages/api-plugin-translations @@ -485,7 +485,7 @@ importers: simpl-schema: ^1.12.0 dependencies: '@reactioncommerce/api-plugin-address-validation': link:../api-plugin-address-validation - '@reactioncommerce/api-plugin-tags': link:../api-plugin-tags + '@reactioncommerce/api-plugin-tags': 1.2.0_graphql@15.8.0 '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random @@ -673,6 +673,27 @@ importers: '@reactioncommerce/babel-remove-es-create-require': 1.0.0_@babel+core@7.19.0 '@reactioncommerce/data-factory': 1.0.1 + packages/api-plugin-fulfillment: + specifiers: + '@reactioncommerce/api-plugin-carts': ^1.0.0 + '@reactioncommerce/api-utils': ^1.16.9 + '@reactioncommerce/data-factory': ~1.0.1 + '@reactioncommerce/logger': ^1.1.3 + '@reactioncommerce/random': ^1.0.2 + '@reactioncommerce/reaction-error': ^1.0.1 + lodash: ^4.17.21 + simpl-schema: ^1.12.2 + dependencies: + '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/logger': link:../logger + '@reactioncommerce/random': link:../random + '@reactioncommerce/reaction-error': link:../reaction-error + lodash: 4.17.21 + simpl-schema: 1.12.3 + devDependencies: + '@reactioncommerce/api-plugin-carts': link:../api-plugin-carts + '@reactioncommerce/data-factory': 1.0.1 + packages/api-plugin-i18n: specifiers: '@reactioncommerce/api-utils': ^1.16.5 @@ -4593,6 +4614,108 @@ packages: /@protobufjs/utf8/1.1.0: resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + /@reactioncommerce/api-plugin-products/1.3.0_graphql@14.7.0: + resolution: {integrity: sha512-utK/z9MXJ7qcASwvUlFuej5O5fIDUBi5kWg90am91H5p0wa8NKLXNsVzV/MC8oQDSq+/ljhtAhZQsPOBajxr1g==} + engines: {node: '>=14.18.1'} + dependencies: + '@reactioncommerce/api-utils': 1.16.9_graphql@14.7.0 + '@reactioncommerce/logger': 1.1.5 + '@reactioncommerce/random': 1.0.2 + '@reactioncommerce/reaction-error': 1.0.1 + lodash: 4.17.21 + simpl-schema: 1.12.3 + transitivePeerDependencies: + - graphql + dev: false + + /@reactioncommerce/api-plugin-surcharges/1.1.7_graphql@14.7.0: + resolution: {integrity: sha512-UdaWUmFEUHfCaOAGHs31S3yZGufmD7zO+GpOTlC4U310oQjr+CNQ0b2H8QxmSoUKnjgQfO9SN9bjiF5qUcOU6g==} + engines: {node: '>=14.18.1'} + dependencies: + '@reactioncommerce/api-utils': 1.16.9_graphql@14.7.0 + '@reactioncommerce/random': 1.0.2 + '@reactioncommerce/reaction-error': 1.0.1 + simpl-schema: 1.12.3 + transitivePeerDependencies: + - graphql + dev: false + + /@reactioncommerce/api-plugin-tags/1.2.0_graphql@14.7.0: + resolution: {integrity: sha512-6SgTt+AvGJs/ThRq8tMEiEF0HvWShJhzRNjInM+CJ/mz/1U9fDVRxhecwWI7iYG9gqs30XNFRhjgNdJxJVO0SA==} + engines: {node: '>=14.18.1'} + dependencies: + '@reactioncommerce/api-utils': 1.16.9_graphql@14.7.0 + '@reactioncommerce/data-factory': 1.0.1 + '@reactioncommerce/db-version-check': 1.0.0 + '@reactioncommerce/file-collections': 0.9.3 + '@reactioncommerce/random': 1.0.2 + '@reactioncommerce/reaction-error': 1.0.1 + lodash: 4.17.21 + simpl-schema: 1.10.2 + transitivePeerDependencies: + - encoding + - graphql + - supports-color + dev: false + + /@reactioncommerce/api-plugin-tags/1.2.0_graphql@15.8.0: + resolution: {integrity: sha512-6SgTt+AvGJs/ThRq8tMEiEF0HvWShJhzRNjInM+CJ/mz/1U9fDVRxhecwWI7iYG9gqs30XNFRhjgNdJxJVO0SA==} + engines: {node: '>=14.18.1'} + dependencies: + '@reactioncommerce/api-utils': 1.16.9_graphql@15.8.0 + '@reactioncommerce/data-factory': 1.0.1 + '@reactioncommerce/db-version-check': 1.0.0 + '@reactioncommerce/file-collections': 0.9.3 + '@reactioncommerce/random': 1.0.2 + '@reactioncommerce/reaction-error': 1.0.1 + lodash: 4.17.21 + simpl-schema: 1.10.2 + transitivePeerDependencies: + - encoding + - graphql + - supports-color + dev: false + + /@reactioncommerce/api-utils/1.16.9_graphql@14.7.0: + resolution: {integrity: sha512-9jB684K3GISSkkTeiMIniYiFgAtHL1AosSD2GPVpGSpFIUVbEL93e8owEadSIugmJt8yOb3m1+YdThW+kvXlzQ==} + engines: {node: '>=12.14.1'} + dependencies: + '@jest/globals': 26.6.2 + '@reactioncommerce/logger': 1.1.5 + '@reactioncommerce/random': 1.0.2 + '@reactioncommerce/reaction-error': 1.0.1 + accounting-js: 1.1.1 + callsite: 1.0.0 + envalid: 6.0.2 + graphql-fields: 2.0.3 + graphql-relay: 0.9.0_graphql@14.7.0 + lodash: 4.17.21 + ramda: 0.27.2 + transliteration: 2.3.5 + transitivePeerDependencies: + - graphql + dev: false + + /@reactioncommerce/api-utils/1.16.9_graphql@15.8.0: + resolution: {integrity: sha512-9jB684K3GISSkkTeiMIniYiFgAtHL1AosSD2GPVpGSpFIUVbEL93e8owEadSIugmJt8yOb3m1+YdThW+kvXlzQ==} + engines: {node: '>=12.14.1'} + dependencies: + '@jest/globals': 26.6.2 + '@reactioncommerce/logger': 1.1.5 + '@reactioncommerce/random': 1.0.2 + '@reactioncommerce/reaction-error': 1.0.1 + accounting-js: 1.1.1 + callsite: 1.0.0 + envalid: 6.0.2 + graphql-fields: 2.0.3 + graphql-relay: 0.9.0_graphql@15.8.0 + lodash: 4.17.21 + ramda: 0.27.2 + transliteration: 2.3.5 + transitivePeerDependencies: + - graphql + dev: false + /@reactioncommerce/babel-remove-es-create-require/1.0.0_@babel+core@7.19.0: resolution: {integrity: sha512-yR1vMj76hK5D9/VcXjFpk2OMoYpQdbvEgGcJ79cEzTL/jkFwIs53Zl6PNBjcC608P8qHl5eypEYRwG7CCGLzwQ==} peerDependencies: @@ -4623,6 +4746,10 @@ packages: transitivePeerDependencies: - supports-color + /@reactioncommerce/db-version-check/1.0.0: + resolution: {integrity: sha512-68ypfiCvdsmMaLmzNpXjN52SmgyHHO2IjLOscBbqBlvq/rFCVLLxV5/WkiFnedmNesFoXCfwYhyGWyjwaqicuw==} + dev: false + /@reactioncommerce/eslint-config/2.2.2_ihki3xsivcrciip6mkoznh225e: resolution: {integrity: sha512-E4BlfLmQBr5Sjt3LP9tAbgCKXZ2rQ4/Lpvle6khHUcuh33NcuBEInN+uStY6koPHAvnKgwn6MZ0LTYzVnKwSVA==} engines: {node: '>=8.1.0'} @@ -4648,11 +4775,43 @@ packages: eslint-plugin-you-dont-need-lodash-underscore: 6.12.0 dev: true + /@reactioncommerce/file-collections/0.9.3: + resolution: {integrity: sha512-8HLEjvlhtgpOAQYQeHA6UGYJfbYoSuUtQAyo0E0DUUpgeViXiWgQ+T4DIK3NbUinlvA4Ny8FDtZCNEg5eHCfMw==} + dependencies: + '@babel/runtime-corejs2': 7.19.0 + content-disposition: 0.5.4 + debug: 4.3.4 + extend: 3.0.2 + path-parser: 6.1.0 + query-string: 6.14.1 + tus-js-client: 1.8.0 + tus-node-server: 0.3.2 + transitivePeerDependencies: + - encoding + - supports-color + dev: false + + /@reactioncommerce/logger/1.1.5: + resolution: {integrity: sha512-fG6wohv9NoX6ZjNzs0YcFRFxHyndO4VitMCrbz+8HrnM7LNJtQRzrUcR58RptagYixZ4GyiHY6WhmIfrMpNvLw==} + dependencies: + bunyan: 1.8.15 + bunyan-format: 0.2.1 + node-loggly-bulk: 2.2.5 + dev: false + /@reactioncommerce/nodemailer/5.0.5: resolution: {integrity: sha512-u4ontTETlROmLglkMDyouMXlX62NXOGfOUAd75Ilk3W4tcsRjRXX+g5C5B4mBCCcJB0wHn1yh/a4pOYkn81vUQ==} engines: {node: '>=4.0.0'} dev: false + /@reactioncommerce/random/1.0.2: + resolution: {integrity: sha512-02eARZh6FQ45tEXXZUwnz90XWQasLpnV140CcuOl/L3dhEL4Emu2oCZemVhKvNb2JMmm5vOG2Y0CUb12xAx73w==} + dev: false + + /@reactioncommerce/reaction-error/1.0.1: + resolution: {integrity: sha512-l1HyDTFw4m1j8EPWNA/bbF2GZZaLYQjwmgniSjjM1cgnhfjCqeLywi501oPkmH8E84Jh0EH+4ADhNCcZ4m4gug==} + dev: false + /@sinclair/typebox/0.24.41: resolution: {integrity: sha512-TJCgQurls4FipFvHeC+gfAzb+GGstL0TDwYJKQVtTeSvJIznWzP7g3bAd5gEBlr8+bIxqnWS9VGVWREDhmE8jA==} dev: true @@ -9015,6 +9174,15 @@ packages: graphql: 14.7.0 dev: false + /graphql-relay/0.9.0_graphql@15.8.0: + resolution: {integrity: sha512-yNJLCqcjz0XpzpmmckRJCSK8a2ZLwTurwrQ09UyGftONh52PbrGpK1UO4yspvj0c7pC+jkN4ZUqVXG3LRrWkXQ==} + engines: {node: ^12.20.0 || ^14.15.0 || >= 15.9.0} + peerDependencies: + graphql: ^15.5.3 + dependencies: + graphql: 15.8.0 + dev: false + /graphql-schema-linter/3.0.0_graphql@16.6.0: resolution: {integrity: sha512-nXsyFvcqrD9/QWgimvJjvRBsUXHw8O7XoPLhpnGZd9eX6r2ZXsOBCGhTzF9ZrFtWosCBEfBGU2xpFUPe0qCHVg==} hasBin: true @@ -9066,6 +9234,11 @@ packages: dependencies: iterall: 1.3.0 + /graphql/15.8.0: + resolution: {integrity: sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw==} + engines: {node: '>= 10.x'} + dev: false + /graphql/16.6.0: resolution: {integrity: sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} @@ -12360,6 +12533,10 @@ packages: engines: {node: '>=8'} dev: false + /ramda/0.27.2: + resolution: {integrity: sha512-SbiLPU40JuJniHexQSAgad32hfwd+DRUdwF2PlVuI5RZD0/vahUco7R8vD86J/tcEKKF9vZrUVwgtmGCqlCKyA==} + dev: false + /ramda/0.28.0: resolution: {integrity: sha512-9QnLuG/kPVgWvMQ4aODhsBUFKOUmnbUnsSXACv+NCQZcHbeb+v8Lodp8OVxtRULN1/xOyYLLaL6npE6dMq5QTA==} dev: false From 584890382ae4f7c55a9632317ed3c1c65ed6ce37 Mon Sep 17 00:00:00 2001 From: Sujith Date: Mon, 17 Oct 2022 23:35:48 +0530 Subject: [PATCH 02/71] feat: initial commit fulfillment base Signed-off-by: Sujith From b57ea4aed9cd5bc357cb77609846d58e00a06254 Mon Sep 17 00:00:00 2001 From: Sujith Date: Tue, 18 Oct 2022 16:35:01 +0530 Subject: [PATCH 03/71] Merge remote-tracking branch 'origin/01-fulfillment-base' into 01-fulfillment-base Signed-off-by: Sujith From 0de0992f0e382fd9afb55c4bc713751ceae256ab Mon Sep 17 00:00:00 2001 From: Sujith Date: Tue, 18 Oct 2022 16:38:24 +0530 Subject: [PATCH 04/71] fix: pnpm-lock Signed-off-by: Sujith --- pnpm-lock.yaml | 204 +++++++++++-------------------------------------- 1 file changed, 44 insertions(+), 160 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1055843771e..a54914d1c58 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -225,16 +225,16 @@ importers: '@reactioncommerce/api-plugin-payments-example': link:../../packages/api-plugin-payments-example '@reactioncommerce/api-plugin-payments-stripe-sca': link:../../packages/api-plugin-payments-stripe-sca '@reactioncommerce/api-plugin-pricing-simple': link:../../packages/api-plugin-pricing-simple - '@reactioncommerce/api-plugin-products': 1.3.0_graphql@14.7.0 + '@reactioncommerce/api-plugin-products': link:../../packages/api-plugin-products '@reactioncommerce/api-plugin-settings': link:../../packages/api-plugin-settings '@reactioncommerce/api-plugin-shipments': link:../../packages/api-plugin-shipments '@reactioncommerce/api-plugin-shipments-flat-rate': link:../../packages/api-plugin-shipments-flat-rate '@reactioncommerce/api-plugin-shops': link:../../packages/api-plugin-shops '@reactioncommerce/api-plugin-simple-schema': link:../../packages/api-plugin-simple-schema '@reactioncommerce/api-plugin-sitemap-generator': link:../../packages/api-plugin-sitemap-generator - '@reactioncommerce/api-plugin-surcharges': 1.1.7_graphql@14.7.0 + '@reactioncommerce/api-plugin-surcharges': link:../../packages/api-plugin-surcharges '@reactioncommerce/api-plugin-system-information': link:../../packages/api-plugin-system-information - '@reactioncommerce/api-plugin-tags': 1.2.0_graphql@14.7.0 + '@reactioncommerce/api-plugin-tags': link:../../packages/api-plugin-tags '@reactioncommerce/api-plugin-taxes': link:../../packages/api-plugin-taxes '@reactioncommerce/api-plugin-taxes-flat-rate': link:../../packages/api-plugin-taxes-flat-rate '@reactioncommerce/api-plugin-translations': link:../../packages/api-plugin-translations @@ -485,7 +485,7 @@ importers: simpl-schema: ^1.12.0 dependencies: '@reactioncommerce/api-plugin-address-validation': link:../api-plugin-address-validation - '@reactioncommerce/api-plugin-tags': 1.2.0_graphql@15.8.0 + '@reactioncommerce/api-plugin-tags': link:../api-plugin-tags '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random @@ -694,6 +694,46 @@ importers: '@reactioncommerce/api-plugin-carts': link:../api-plugin-carts '@reactioncommerce/data-factory': 1.0.1 + packages/api-plugin-fulfillment-method-shipping-flat-rate: + specifiers: + '@reactioncommerce/api-utils': ^1.16.9 + '@reactioncommerce/logger': ^1.1.3 + '@reactioncommerce/random': ~1.0.2 + '@reactioncommerce/reaction-error': ^1.0.1 + simpl-schema: ^1.12.2 + dependencies: + '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/logger': link:../logger + '@reactioncommerce/random': link:../random + '@reactioncommerce/reaction-error': link:../reaction-error + simpl-schema: 1.12.3 + + packages/api-plugin-fulfillment-method-shipping-ups: + specifiers: + '@reactioncommerce/api-utils': ^1.16.9 + '@reactioncommerce/reaction-error': ^1.0.1 + simpl-schema: ^1.12.2 + dependencies: + '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/reaction-error': link:../reaction-error + simpl-schema: 1.12.3 + + packages/api-plugin-fulfillment-type-shipping: + specifiers: + '@reactioncommerce/api-utils': ^1.16.9 + '@reactioncommerce/logger': ^1.1.3 + '@reactioncommerce/random': ~1.0.2 + '@reactioncommerce/reaction-error': ^1.0.1 + lodash: ^4.17.21 + simpl-schema: ^1.12.2 + dependencies: + '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/logger': link:../logger + '@reactioncommerce/random': link:../random + '@reactioncommerce/reaction-error': link:../reaction-error + lodash: 4.17.21 + simpl-schema: 1.12.3 + packages/api-plugin-i18n: specifiers: '@reactioncommerce/api-utils': ^1.16.5 @@ -4614,108 +4654,6 @@ packages: /@protobufjs/utf8/1.1.0: resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} - /@reactioncommerce/api-plugin-products/1.3.0_graphql@14.7.0: - resolution: {integrity: sha512-utK/z9MXJ7qcASwvUlFuej5O5fIDUBi5kWg90am91H5p0wa8NKLXNsVzV/MC8oQDSq+/ljhtAhZQsPOBajxr1g==} - engines: {node: '>=14.18.1'} - dependencies: - '@reactioncommerce/api-utils': 1.16.9_graphql@14.7.0 - '@reactioncommerce/logger': 1.1.5 - '@reactioncommerce/random': 1.0.2 - '@reactioncommerce/reaction-error': 1.0.1 - lodash: 4.17.21 - simpl-schema: 1.12.3 - transitivePeerDependencies: - - graphql - dev: false - - /@reactioncommerce/api-plugin-surcharges/1.1.7_graphql@14.7.0: - resolution: {integrity: sha512-UdaWUmFEUHfCaOAGHs31S3yZGufmD7zO+GpOTlC4U310oQjr+CNQ0b2H8QxmSoUKnjgQfO9SN9bjiF5qUcOU6g==} - engines: {node: '>=14.18.1'} - dependencies: - '@reactioncommerce/api-utils': 1.16.9_graphql@14.7.0 - '@reactioncommerce/random': 1.0.2 - '@reactioncommerce/reaction-error': 1.0.1 - simpl-schema: 1.12.3 - transitivePeerDependencies: - - graphql - dev: false - - /@reactioncommerce/api-plugin-tags/1.2.0_graphql@14.7.0: - resolution: {integrity: sha512-6SgTt+AvGJs/ThRq8tMEiEF0HvWShJhzRNjInM+CJ/mz/1U9fDVRxhecwWI7iYG9gqs30XNFRhjgNdJxJVO0SA==} - engines: {node: '>=14.18.1'} - dependencies: - '@reactioncommerce/api-utils': 1.16.9_graphql@14.7.0 - '@reactioncommerce/data-factory': 1.0.1 - '@reactioncommerce/db-version-check': 1.0.0 - '@reactioncommerce/file-collections': 0.9.3 - '@reactioncommerce/random': 1.0.2 - '@reactioncommerce/reaction-error': 1.0.1 - lodash: 4.17.21 - simpl-schema: 1.10.2 - transitivePeerDependencies: - - encoding - - graphql - - supports-color - dev: false - - /@reactioncommerce/api-plugin-tags/1.2.0_graphql@15.8.0: - resolution: {integrity: sha512-6SgTt+AvGJs/ThRq8tMEiEF0HvWShJhzRNjInM+CJ/mz/1U9fDVRxhecwWI7iYG9gqs30XNFRhjgNdJxJVO0SA==} - engines: {node: '>=14.18.1'} - dependencies: - '@reactioncommerce/api-utils': 1.16.9_graphql@15.8.0 - '@reactioncommerce/data-factory': 1.0.1 - '@reactioncommerce/db-version-check': 1.0.0 - '@reactioncommerce/file-collections': 0.9.3 - '@reactioncommerce/random': 1.0.2 - '@reactioncommerce/reaction-error': 1.0.1 - lodash: 4.17.21 - simpl-schema: 1.10.2 - transitivePeerDependencies: - - encoding - - graphql - - supports-color - dev: false - - /@reactioncommerce/api-utils/1.16.9_graphql@14.7.0: - resolution: {integrity: sha512-9jB684K3GISSkkTeiMIniYiFgAtHL1AosSD2GPVpGSpFIUVbEL93e8owEadSIugmJt8yOb3m1+YdThW+kvXlzQ==} - engines: {node: '>=12.14.1'} - dependencies: - '@jest/globals': 26.6.2 - '@reactioncommerce/logger': 1.1.5 - '@reactioncommerce/random': 1.0.2 - '@reactioncommerce/reaction-error': 1.0.1 - accounting-js: 1.1.1 - callsite: 1.0.0 - envalid: 6.0.2 - graphql-fields: 2.0.3 - graphql-relay: 0.9.0_graphql@14.7.0 - lodash: 4.17.21 - ramda: 0.27.2 - transliteration: 2.3.5 - transitivePeerDependencies: - - graphql - dev: false - - /@reactioncommerce/api-utils/1.16.9_graphql@15.8.0: - resolution: {integrity: sha512-9jB684K3GISSkkTeiMIniYiFgAtHL1AosSD2GPVpGSpFIUVbEL93e8owEadSIugmJt8yOb3m1+YdThW+kvXlzQ==} - engines: {node: '>=12.14.1'} - dependencies: - '@jest/globals': 26.6.2 - '@reactioncommerce/logger': 1.1.5 - '@reactioncommerce/random': 1.0.2 - '@reactioncommerce/reaction-error': 1.0.1 - accounting-js: 1.1.1 - callsite: 1.0.0 - envalid: 6.0.2 - graphql-fields: 2.0.3 - graphql-relay: 0.9.0_graphql@15.8.0 - lodash: 4.17.21 - ramda: 0.27.2 - transliteration: 2.3.5 - transitivePeerDependencies: - - graphql - dev: false - /@reactioncommerce/babel-remove-es-create-require/1.0.0_@babel+core@7.19.0: resolution: {integrity: sha512-yR1vMj76hK5D9/VcXjFpk2OMoYpQdbvEgGcJ79cEzTL/jkFwIs53Zl6PNBjcC608P8qHl5eypEYRwG7CCGLzwQ==} peerDependencies: @@ -4746,10 +4684,6 @@ packages: transitivePeerDependencies: - supports-color - /@reactioncommerce/db-version-check/1.0.0: - resolution: {integrity: sha512-68ypfiCvdsmMaLmzNpXjN52SmgyHHO2IjLOscBbqBlvq/rFCVLLxV5/WkiFnedmNesFoXCfwYhyGWyjwaqicuw==} - dev: false - /@reactioncommerce/eslint-config/2.2.2_ihki3xsivcrciip6mkoznh225e: resolution: {integrity: sha512-E4BlfLmQBr5Sjt3LP9tAbgCKXZ2rQ4/Lpvle6khHUcuh33NcuBEInN+uStY6koPHAvnKgwn6MZ0LTYzVnKwSVA==} engines: {node: '>=8.1.0'} @@ -4775,43 +4709,11 @@ packages: eslint-plugin-you-dont-need-lodash-underscore: 6.12.0 dev: true - /@reactioncommerce/file-collections/0.9.3: - resolution: {integrity: sha512-8HLEjvlhtgpOAQYQeHA6UGYJfbYoSuUtQAyo0E0DUUpgeViXiWgQ+T4DIK3NbUinlvA4Ny8FDtZCNEg5eHCfMw==} - dependencies: - '@babel/runtime-corejs2': 7.19.0 - content-disposition: 0.5.4 - debug: 4.3.4 - extend: 3.0.2 - path-parser: 6.1.0 - query-string: 6.14.1 - tus-js-client: 1.8.0 - tus-node-server: 0.3.2 - transitivePeerDependencies: - - encoding - - supports-color - dev: false - - /@reactioncommerce/logger/1.1.5: - resolution: {integrity: sha512-fG6wohv9NoX6ZjNzs0YcFRFxHyndO4VitMCrbz+8HrnM7LNJtQRzrUcR58RptagYixZ4GyiHY6WhmIfrMpNvLw==} - dependencies: - bunyan: 1.8.15 - bunyan-format: 0.2.1 - node-loggly-bulk: 2.2.5 - dev: false - /@reactioncommerce/nodemailer/5.0.5: resolution: {integrity: sha512-u4ontTETlROmLglkMDyouMXlX62NXOGfOUAd75Ilk3W4tcsRjRXX+g5C5B4mBCCcJB0wHn1yh/a4pOYkn81vUQ==} engines: {node: '>=4.0.0'} dev: false - /@reactioncommerce/random/1.0.2: - resolution: {integrity: sha512-02eARZh6FQ45tEXXZUwnz90XWQasLpnV140CcuOl/L3dhEL4Emu2oCZemVhKvNb2JMmm5vOG2Y0CUb12xAx73w==} - dev: false - - /@reactioncommerce/reaction-error/1.0.1: - resolution: {integrity: sha512-l1HyDTFw4m1j8EPWNA/bbF2GZZaLYQjwmgniSjjM1cgnhfjCqeLywi501oPkmH8E84Jh0EH+4ADhNCcZ4m4gug==} - dev: false - /@sinclair/typebox/0.24.41: resolution: {integrity: sha512-TJCgQurls4FipFvHeC+gfAzb+GGstL0TDwYJKQVtTeSvJIznWzP7g3bAd5gEBlr8+bIxqnWS9VGVWREDhmE8jA==} dev: true @@ -9174,15 +9076,6 @@ packages: graphql: 14.7.0 dev: false - /graphql-relay/0.9.0_graphql@15.8.0: - resolution: {integrity: sha512-yNJLCqcjz0XpzpmmckRJCSK8a2ZLwTurwrQ09UyGftONh52PbrGpK1UO4yspvj0c7pC+jkN4ZUqVXG3LRrWkXQ==} - engines: {node: ^12.20.0 || ^14.15.0 || >= 15.9.0} - peerDependencies: - graphql: ^15.5.3 - dependencies: - graphql: 15.8.0 - dev: false - /graphql-schema-linter/3.0.0_graphql@16.6.0: resolution: {integrity: sha512-nXsyFvcqrD9/QWgimvJjvRBsUXHw8O7XoPLhpnGZd9eX6r2ZXsOBCGhTzF9ZrFtWosCBEfBGU2xpFUPe0qCHVg==} hasBin: true @@ -9234,11 +9127,6 @@ packages: dependencies: iterall: 1.3.0 - /graphql/15.8.0: - resolution: {integrity: sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw==} - engines: {node: '>= 10.x'} - dev: false - /graphql/16.6.0: resolution: {integrity: sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} @@ -12533,10 +12421,6 @@ packages: engines: {node: '>=8'} dev: false - /ramda/0.27.2: - resolution: {integrity: sha512-SbiLPU40JuJniHexQSAgad32hfwd+DRUdwF2PlVuI5RZD0/vahUco7R8vD86J/tcEKKF9vZrUVwgtmGCqlCKyA==} - dev: false - /ramda/0.28.0: resolution: {integrity: sha512-9QnLuG/kPVgWvMQ4aODhsBUFKOUmnbUnsSXACv+NCQZcHbeb+v8Lodp8OVxtRULN1/xOyYLLaL6npE6dMq5QTA==} dev: false From 18c9870d44cf48142d033864ce34cae009628abb Mon Sep 17 00:00:00 2001 From: Sujith Date: Thu, 20 Oct 2022 01:05:55 +0530 Subject: [PATCH 05/71] fix: to generate function name in camel-case Signed-off-by: Sujith --- .../queries/getFulfillmentMethodsWithQuotes.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethodsWithQuotes.js b/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethodsWithQuotes.js index 02b1428e6c2..dd9759e4d7a 100644 --- a/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethodsWithQuotes.js +++ b/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethodsWithQuotes.js @@ -4,6 +4,21 @@ import extendCommonOrder from "../util/extendCommonOrder.js"; const logCtx = { name: "fulfillment", file: "getFulfillmentWithQuotes" }; +/** + * @name getTitleCaseOfWord + * @method + * @summary converts the given word to Title case + * @param {String} inputWord - input word + * @returns {String} return Title case of word + * @private + */ +function getTitleCaseOfWord(inputWord) { + let outWord = String(inputWord); + outWord = outWord.toLowerCase(); + outWord = outWord.charAt(0).toUpperCase() + outWord.slice(1); + return outWord; +} + /** * @name getFulfillmentMethodsWithQuotes * @method @@ -27,7 +42,8 @@ export default async function getFulfillmentMethodsWithQuotes(commonOrder, conte const fulfillmentTypeInGroup = commonOrder.fulfillmentType; if (!fulfillmentTypeInGroup) throw new ReactionError("not-found", "Fulfillment type not found in commonOrder"); - const functionTypesToCall = `getFulfillmentMethodsWithQuotes${fulfillmentTypeInGroup}`; + const fulfillmentTypeTitleCase = getTitleCaseOfWord(fulfillmentTypeInGroup); + const functionTypesToCall = `getFulfillmentMethodsWithQuotes${fulfillmentTypeTitleCase}`; const funcs = context.getFunctionsOfType(functionTypesToCall); if (!funcs || !Array.isArray(funcs) || !funcs.length) throw new ReactionError("not-found", `No methods for Fulfillment type ${fulfillmentTypeInGroup}`); From 3a2fa085064af0db8b80f57565c7bc81f3d29079 Mon Sep 17 00:00:00 2001 From: Sujith Date: Mon, 24 Oct 2022 13:42:06 +0530 Subject: [PATCH 06/71] feat: createFFType test Signed-off-by: Sujith --- .../createFulfillmentType.test.js.snap | 3 + .../src/mutations/createFulfillmentType.js | 1 + .../mutations/createFulfillmentType.test.js | 69 +++++++++++++++++++ 3 files changed, 73 insertions(+) create mode 100644 packages/api-plugin-fulfillment/src/mutations/__snapshots__/createFulfillmentType.test.js.snap create mode 100644 packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.test.js diff --git a/packages/api-plugin-fulfillment/src/mutations/__snapshots__/createFulfillmentType.test.js.snap b/packages/api-plugin-fulfillment/src/mutations/__snapshots__/createFulfillmentType.test.js.snap new file mode 100644 index 00000000000..828ff99c293 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/__snapshots__/createFulfillmentType.test.js.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`throws if required fields are not supplied 1`] = `"Enabled is required"`; diff --git a/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.js b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.js index bd2bcadfcfa..50f92af8c19 100644 --- a/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.js +++ b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.js @@ -11,6 +11,7 @@ import { fulfillmentTypeSchema } from "../simpleSchemas.js"; */ export default async function createFulfillmentType(context, input) { const cleanedInput = fulfillmentTypeSchema.clean(input); + if (!cleanedInput.provider) cleanedInput.provider = {}; cleanedInput.provider.name = cleanedInput.name; if (cleanedInput.method) { diff --git a/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.test.js b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.test.js new file mode 100644 index 00000000000..c14d85fb3a8 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.test.js @@ -0,0 +1,69 @@ +import ReactionError from "@reactioncommerce/reaction-error"; +import mockCollection from "@reactioncommerce/api-utils/tests/mockCollection.js"; +import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; +import createFulfillmentTypeMutation from "./createFulfillmentType.js"; + +mockContext.validatePermissions = jest.fn().mockName("validatePermissions"); +mockContext.collections.Fulfillment = mockCollection("Fulfillment"); +mockContext.validatePermissions.mockReturnValueOnce(Promise.resolve(null)); + +test("throws if required fields are not supplied", async () => { + const fulfillmentTypeInput = { + shopId: "SHOP_ID", + name: "fulfillmentType123" + }; + + await expect(createFulfillmentTypeMutation(mockContext, fulfillmentTypeInput)).rejects.toThrowErrorMatchingSnapshot(); +}); + +test("throws if the fulfillmentType added already exists", async () => { + mockContext.collections.Fulfillment.findOne.mockReturnValueOnce(Promise.resolve({ + _id: "fulfillment123", + name: "Default Shipping Provider", + shopId: "SHOP_ID", + provider: { + enabled: true, + label: "Shipping", + name: "shipping" + }, + fulfillmentType: "shipping" + })); + + const fulfillmentTypeInput = { + shopId: "SHOP_ID", + name: "fulfillmentType123", + fulfillmentType: "shipping", + provider: { + enabled: true, + label: "Shipping", + name: "shipping" + } + }; + const expectedError = new ReactionError("invalid-parameter", "Fulfillment Type already exists"); + await expect(createFulfillmentTypeMutation(mockContext, fulfillmentTypeInput)).rejects.toThrow(expectedError); +}); + +test("add a new fulfillment type", async () => { + mockContext.collections.Fulfillment.findOne.mockReturnValueOnce(Promise.resolve(undefined)); + mockContext.collections.Fulfillment.insertOne.mockReturnValueOnce(Promise.resolve({ result: { insertedCount: 1 } })); + + const fulfillmentTypeInput = { + shopId: "SHOP_ID", + name: "fulfillmentType123", + fulfillmentType: "shipping", + provider: { + enabled: true, + label: "Shipping", + name: "shipping" + } + }; + + const result = await createFulfillmentTypeMutation(mockContext, fulfillmentTypeInput); + + expect(result).toEqual({ + group: { + name: "fulfillmentType123", + fulfillmentType: "shipping" + } + }); +}); From 4acb9e1c7aa3b6c7909887f4f78d2061d3c8e441 Mon Sep 17 00:00:00 2001 From: Sujith Date: Mon, 24 Oct 2022 23:25:44 +0530 Subject: [PATCH 07/71] feat: ff-type tests Signed-off-by: Sujith --- ...lectFulfillmentOptionForGroup.test.js.snap | 5 + .../updateFulfillmentMethod.test.js.snap | 3 + ...ateFulfillmentOptionsForGroup.test.js.snap | 3 + .../updateFulfillmentType.test.js.snap | 3 + .../selectFulfillmentOptionForGroup.test.js | 67 +++++++ .../mutations/updateFulfillmentMethod.test.js | 136 ++++++++++++++ .../updateFulfillmentOptionsForGroup.test.js | 171 ++++++++++++++++++ .../src/mutations/updateFulfillmentType.js | 3 +- .../mutations/updateFulfillmentType.test.js | 44 +++++ 9 files changed, 433 insertions(+), 2 deletions(-) create mode 100644 packages/api-plugin-fulfillment/src/mutations/__snapshots__/selectFulfillmentOptionForGroup.test.js.snap create mode 100644 packages/api-plugin-fulfillment/src/mutations/__snapshots__/updateFulfillmentMethod.test.js.snap create mode 100644 packages/api-plugin-fulfillment/src/mutations/__snapshots__/updateFulfillmentOptionsForGroup.test.js.snap create mode 100644 packages/api-plugin-fulfillment/src/mutations/__snapshots__/updateFulfillmentType.test.js.snap create mode 100644 packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.test.js create mode 100644 packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.test.js create mode 100644 packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.test.js create mode 100644 packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.test.js diff --git a/packages/api-plugin-fulfillment/src/mutations/__snapshots__/selectFulfillmentOptionForGroup.test.js.snap b/packages/api-plugin-fulfillment/src/mutations/__snapshots__/selectFulfillmentOptionForGroup.test.js.snap new file mode 100644 index 00000000000..c47469bdf28 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/__snapshots__/selectFulfillmentOptionForGroup.test.js.snap @@ -0,0 +1,5 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`throws if there is no fulfillment group with the given ID 1`] = `"Fulfillment group with ID group2 not found in cart with ID cartId"`; + +exports[`throws if there is no fulfillment method with the given ID among the options 1`] = `"Fulfillment option with method ID invalid-method not found in cart with ID cartId"`; diff --git a/packages/api-plugin-fulfillment/src/mutations/__snapshots__/updateFulfillmentMethod.test.js.snap b/packages/api-plugin-fulfillment/src/mutations/__snapshots__/updateFulfillmentMethod.test.js.snap new file mode 100644 index 00000000000..d165ef0089d --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/__snapshots__/updateFulfillmentMethod.test.js.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`throws if required fields are not supplied 1`] = `"Method ID is required"`; diff --git a/packages/api-plugin-fulfillment/src/mutations/__snapshots__/updateFulfillmentOptionsForGroup.test.js.snap b/packages/api-plugin-fulfillment/src/mutations/__snapshots__/updateFulfillmentOptionsForGroup.test.js.snap new file mode 100644 index 00000000000..45e9c989519 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/__snapshots__/updateFulfillmentOptionsForGroup.test.js.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`throws if there is no fulfillment group with the given ID 1`] = `"Fulfillment group with ID group2 not found in cart with ID cartId"`; diff --git a/packages/api-plugin-fulfillment/src/mutations/__snapshots__/updateFulfillmentType.test.js.snap b/packages/api-plugin-fulfillment/src/mutations/__snapshots__/updateFulfillmentType.test.js.snap new file mode 100644 index 00000000000..696b1da29dc --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/__snapshots__/updateFulfillmentType.test.js.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`throws if required fields are not supplied 1`] = `"Name is required"`; diff --git a/packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.test.js b/packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.test.js new file mode 100644 index 00000000000..9068349c76d --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.test.js @@ -0,0 +1,67 @@ +import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; +import selectFulfillmentOptionForGroup from "./selectFulfillmentOptionForGroup.js"; + +jest.mock("../util/getCartById", () => jest.fn().mockImplementation(() => Promise.resolve({ + _id: "cartId", + shipping: [{ + _id: "group1", + itemIds: ["123"], + shipmentQuotes: [{ + rate: 0, + method: { + _id: "valid-method" + } + }], + type: "shipping" + }] +}))); + +beforeAll(() => { + if (!mockContext.mutations.saveCart) { + mockContext.mutations.saveCart = jest.fn().mockName("context.mutations.saveCart").mockImplementation(async (_, cart) => cart); + } +}); + +test("selects an existing shipping method", async () => { + const result = await selectFulfillmentOptionForGroup(mockContext, { + cartId: "cartId", + fulfillmentGroupId: "group1", + fulfillmentMethodId: "valid-method" + }); + expect(result).toEqual({ + cart: { + _id: "cartId", + shipping: [{ + _id: "group1", + itemIds: ["123"], + shipmentQuotes: [{ + rate: 0, + method: { + _id: "valid-method" + } + }], + type: "shipping", + shipmentMethod: { + _id: "valid-method" + } + }], + updatedAt: jasmine.any(Date) + } + }); +}); + +test("throws if there is no fulfillment group with the given ID", async () => { + await expect(selectFulfillmentOptionForGroup(mockContext, { + cartId: "cartId", + fulfillmentGroupId: "group2", + fulfillmentMethodId: "valid-method" + })).rejects.toThrowErrorMatchingSnapshot(); +}); + +test("throws if there is no fulfillment method with the given ID among the options", async () => { + await expect(selectFulfillmentOptionForGroup(mockContext, { + cartId: "cartId", + fulfillmentGroupId: "group1", + fulfillmentMethodId: "invalid-method" + })).rejects.toThrowErrorMatchingSnapshot(); +}); diff --git a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.test.js b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.test.js new file mode 100644 index 00000000000..0a230020809 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.test.js @@ -0,0 +1,136 @@ +import ReactionError from "@reactioncommerce/reaction-error"; +import mockCollection from "@reactioncommerce/api-utils/tests/mockCollection.js"; +import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; +import updateFulfillmentMethodMutation from "./updateFulfillmentMethod.js"; + +mockContext.validatePermissions = jest.fn().mockName("validatePermissions"); +mockContext.collections.Fulfillment = mockCollection("Fulfillment"); +mockContext.validatePermissions.mockReturnValueOnce(Promise.resolve(null)); + +test("throws if required fields are not supplied", async () => { + const fulfillmentMethodInput = { + fulfillmentTypeId: "fulfillmentType01", + shopId: "SHOP_ID", + method: { + fulfillmentTypes: ["Shipping"], + group: "Ground", + handling: 99, + enabled: true, + label: "ups", + name: "ups", + rate: 99 + } + }; + + await expect(updateFulfillmentMethodMutation(mockContext, fulfillmentMethodInput)).rejects.toThrowErrorMatchingSnapshot(); +}); + +test("throws if the fulfillmentType does not exists", async () => { + mockContext.collections.Fulfillment.findOne.mockReturnValueOnce(Promise.resolve(undefined)); + + const fulfillmentMethodInput = { + fulfillmentTypeId: "fulfillmentType01", + shopId: "SHOP_ID", + methodId: "fulfillmentMethodId", + method: { + fulfillmentTypes: ["Shipping"], + group: "Ground", + handling: 99, + enabled: true, + label: "ups", + name: "ups", + rate: 99 + } + }; + const expectedError = new ReactionError("server-error", "Fulfillment Method does not exist"); + await expect(updateFulfillmentMethodMutation(mockContext, fulfillmentMethodInput)).rejects.toThrow(expectedError); +}); + +test("throws if the fulfillmentMethod does not exists", async () => { + mockContext.collections.Fulfillment.findOne.mockReturnValueOnce(Promise.resolve({ + fulfillmentTypeId: "fulfillmentType01", + shopId: "SHOP_ID", + methods: [{ + _id: "fulfillmentMethodId01", + fulfillmentTypes: ["Shipping"], + group: "Ground", + handling: 99, + enabled: true, + label: "ups", + name: "ups", + rate: 99 + }] + })); + + const fulfillmentMethodInput = { + fulfillmentTypeId: "fulfillmentType01", + shopId: "SHOP_ID", + methodId: "fulfillmentMethodId02", + method: { + fulfillmentTypes: ["Shipping"], + group: "Ground", + handling: 99, + enabled: true, + label: "ups", + name: "ups", + rate: 99 + } + }; + const expectedError = new ReactionError("server-error", "Fulfillment Method does not exist"); + await expect(updateFulfillmentMethodMutation(mockContext, fulfillmentMethodInput)).rejects.toThrow(expectedError); +}); + +test("should update an existing fulfillment method", async () => { + mockContext.collections.Fulfillment.updateOne.mockReturnValueOnce(Promise.resolve({ result: { matchedCount: 1 } })); + mockContext.collections.Fulfillment.findOne.mockReturnValueOnce(Promise.resolve({ + fulfillmentTypeId: "fulfillmentType01", + shopId: "SHOP_ID", + fulfillmentType: "Shipping", + methods: [{ + _id: "fulfillmentMethodId01", + fulfillmentTypes: ["Shipping"], + group: "Ground", + handling: 99, + enabled: true, + label: "ups", + name: "ups", + rate: 99, + fulfillmentMethod: "ups" + }] + })); + + const fulfillmentMethodInput = { + fulfillmentTypeId: "fulfillmentType01", + shopId: "SHOP_ID", + methodId: "fulfillmentMethodId01", + method: { + fulfillmentTypes: ["Shipping"], + group: "Ground", + handling: 99, + enabled: true, + label: "ups", + name: "ups", + rate: 99, + fulfillmentMethod: "ups" + } + }; + + const expectedOutput = { + group: { + _id: "fulfillmentMethodId01", + enabled: true, + fulfillmentMethod: "ups", + fulfillmentType: ["Shipping"], + fulfillmentTypes: ["Shipping"], + group: "Ground", + handling: 99, + label: "ups", + name: "ups", + rate: 99 + } + }; + + const result = await updateFulfillmentMethodMutation(mockContext, fulfillmentMethodInput); + + expect(result).toEqual(expectedOutput); +}); diff --git a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.test.js b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.test.js new file mode 100644 index 00000000000..2dbbc8a9b0f --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.test.js @@ -0,0 +1,171 @@ +import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; +import Factory from "../tests/factory.js"; +import updateFulfillmentOptionsForGroup from "./updateFulfillmentOptionsForGroup.js"; + +jest.mock("../util/getCartById", () => jest.fn().mockImplementation(() => Promise.resolve({ + _id: "cartId", + items: [{ + _id: "123", + price: { + amount: 19.99 + }, + priceWhenAdded: { + amount: 19.99 + }, + subtotal: { + amount: 19.99 + } + }], + shipping: [{ + _id: "group1", + itemIds: ["123"], + type: "shipping" + }] +}))); + +const fakeCart = Factory.Cart.makeOne(); +const fakeQuote = Factory.ShipmentQuote.makeOne(); +const mockGetFulfillmentMethodsWithQuotes = jest.fn().mockName("getFulfillmentMethodsWithQuotes"); +const mockGetCommonOrderForCartGroup = jest.fn().mockName("getCommonOrderForCartGroup"); + +beforeAll(() => { + mockContext.queries = { + getFulfillmentMethodsWithQuotes: mockGetFulfillmentMethodsWithQuotes, + getCommonOrderForCartGroup: mockGetCommonOrderForCartGroup + }; + if (!mockContext.mutations.saveCart) { + mockContext.mutations.saveCart = jest.fn().mockName("context.mutations.saveCart").mockImplementation(async (_, cart) => cart); + } + if (!mockContext.mutations.removeMissingItemsFromCart) { + mockContext.mutations.removeMissingItemsFromCart = jest.fn().mockName("context.mutations.removeMissingItemsFromCart"); + } +}); + +beforeEach(() => { + mockGetFulfillmentMethodsWithQuotes.mockClear(); +}); + +test("updates cart properly for empty rates", async () => { + mockGetFulfillmentMethodsWithQuotes.mockReturnValueOnce(Promise.resolve([])); + mockContext.collections.Cart.findOne.mockReturnValueOnce(Promise.resolve(fakeCart)); + + const result = await updateFulfillmentOptionsForGroup(mockContext, { + cartId: "cartId", + fulfillmentGroupId: "group1" + }); + + expect(result).toEqual({ + cart: { + _id: "cartId", + items: [{ + _id: "123", + price: { + amount: 19.99 + }, + priceWhenAdded: { + amount: 19.99 + }, + subtotal: { + amount: 19.99 + } + }], + shipping: [{ + _id: "group1", + itemIds: ["123"], + type: "shipping", + shipmentQuotes: [], + shipmentQuotesQueryStatus: { requestStatus: "pending" } + }], + updatedAt: jasmine.any(Date) + } + }); +}); + +test("updates cart properly for error rates", async () => { + mockGetFulfillmentMethodsWithQuotes.mockReturnValueOnce(Promise.resolve([{ + requestStatus: "error", + shippingProvider: "all", + message: "All requests for shipping methods failed." + }])); + + const result = await updateFulfillmentOptionsForGroup(mockContext, { + cartId: "cartId", + fulfillmentGroupId: "group1" + }); + + expect(result).toEqual({ + cart: { + _id: "cartId", + items: [{ + _id: "123", + price: { + amount: 19.99 + }, + priceWhenAdded: { + amount: 19.99 + }, + subtotal: { + amount: 19.99 + } + }], + shipping: [{ + _id: "group1", + itemIds: ["123"], + type: "shipping", + shipmentQuotes: [], + shipmentQuotesQueryStatus: { + requestStatus: "error", + shippingProvider: "all", + message: "All requests for shipping methods failed." + } + }], + updatedAt: jasmine.any(Date) + } + }); +}); + +test("updates cart properly for success rates", async () => { + mockGetFulfillmentMethodsWithQuotes.mockReturnValueOnce(Promise.resolve([fakeQuote])); + mockContext.collections.Cart.findOne.mockReturnValueOnce(Promise.resolve(fakeCart)); + + const result = await updateFulfillmentOptionsForGroup(mockContext, { + cartId: "cartId", + fulfillmentGroupId: "group1" + }); + + expect(result).toEqual({ + cart: { + _id: "cartId", + items: [{ + _id: "123", + price: { + amount: 19.99 + }, + priceWhenAdded: { + amount: 19.99 + }, + subtotal: { + amount: 19.99 + } + }], + shipping: [{ + _id: "group1", + itemIds: ["123"], + type: "shipping", + shipmentQuotes: [fakeQuote], + shipmentQuotesQueryStatus: { + requestStatus: "success", + numOfShippingMethodsFound: 1 + } + }], + updatedAt: jasmine.any(Date) + } + }); +}); + +test("throws if there is no fulfillment group with the given ID", async () => { + await expect(updateFulfillmentOptionsForGroup(mockContext, { + cartId: "cartId", + fulfillmentGroupId: "group2" + })).rejects.toThrowErrorMatchingSnapshot(); +}); diff --git a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.js b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.js index 089c91e0b4c..8282e3dbb00 100644 --- a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.js +++ b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.js @@ -28,8 +28,7 @@ export default async function updateFulfillmentType(context, input) { inputSchema.validate(cleanedInput); const { fulfillmentGroupId, shopId, name, enabled, label, displayMessageType } = cleanedInput; - const { collections } = context; - const { Fulfillment } = collections; + const { collections: { Fulfillment } } = context; if (!shopId) throw new ReactionError("invalid-parameter", "Shop ID to be updated not provided"); if (!fulfillmentGroupId) throw new ReactionError("invalid-parameter", "FulfillmentType ID to be updated not provided"); diff --git a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.test.js b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.test.js new file mode 100644 index 00000000000..30402070311 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.test.js @@ -0,0 +1,44 @@ +import mockCollection from "@reactioncommerce/api-utils/tests/mockCollection.js"; +import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; +import updateFulfillmentTypeMutation from "./updateFulfillmentType.js"; + +mockContext.validatePermissions = jest.fn().mockName("validatePermissions"); +mockContext.collections.Fulfillment = mockCollection("Fulfillment"); +mockContext.validatePermissions.mockReturnValueOnce(Promise.resolve(null)); + +test("throws if required fields are not supplied", async () => { + const fulfillmentTypeInput = { + fulfillmentGroupId: "fulfillmentGroup01", + shopId: "SHOP_ID", + label: "Shipping" + }; + + await expect(updateFulfillmentTypeMutation(mockContext, fulfillmentTypeInput)).rejects.toThrowErrorMatchingSnapshot(); +}); + + +test("should update an existing fulfillment type", async () => { + mockContext.collections.Fulfillment.updateOne.mockReturnValueOnce(Promise.resolve({ result: { matchedCount: 1 } })); + + const fulfillmentTypeInput = { + fulfillmentGroupId: "fulfillmentGroup01", + shopId: "SHOP_ID", + name: "fulfillmentType123", + enabled: false, + label: "Shipping" + }; + + const expectedOutput = { + fulfillmentGroupId: "fulfillmentGroup01", + shopId: "SHOP_ID", + name: "fulfillmentType123", + enabled: false, + label: "Shipping" + }; + + const result = await updateFulfillmentTypeMutation(mockContext, fulfillmentTypeInput); + + expect(result).toEqual({ + group: expectedOutput + }); +}); From b5edb372e7a1ae65fef8d29c5e303cb81280476f Mon Sep 17 00:00:00 2001 From: Sujith Date: Wed, 26 Oct 2022 15:57:47 +0530 Subject: [PATCH 08/71] fix: review comment fixes Signed-off-by: Sujith --- packages/api-plugin-fulfillment/.gitignore | 61 ----------------- packages/api-plugin-fulfillment/README.md | 67 +------------------ packages/api-plugin-fulfillment/src/index.js | 10 ++- .../src/mutations/createFulfillmentMethod.js | 17 +++-- .../src/mutations/createFulfillmentType.js | 23 ++++--- .../src/mutations/updateFulfillmentMethod.js | 15 ++--- .../updateFulfillmentOptionsForGroup.js | 3 +- .../src/mutations/updateFulfillmentType.js | 8 ++- .../api-plugin-fulfillment/src/preStartup.js | 2 +- .../getFulfillmentMethodsWithQuotes.js | 2 +- .../src/queries/getFulfillmentType.js | 5 +- .../src/registration.js | 6 +- .../resolvers/Query/getFulfillmentTypes.js | 2 +- .../src/resolvers/index.js | 5 -- .../src/simpleSchemas.js | 40 ++++++++++- .../api-plugin-fulfillment/src/startup.js | 3 +- 16 files changed, 94 insertions(+), 175 deletions(-) delete mode 100644 packages/api-plugin-fulfillment/.gitignore diff --git a/packages/api-plugin-fulfillment/.gitignore b/packages/api-plugin-fulfillment/.gitignore deleted file mode 100644 index ad46b30886f..00000000000 --- a/packages/api-plugin-fulfillment/.gitignore +++ /dev/null @@ -1,61 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# TypeScript v1 declaration files -typings/ - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file -.env - -# next.js build output -.next diff --git a/packages/api-plugin-fulfillment/README.md b/packages/api-plugin-fulfillment/README.md index 53aaf9acead..22ab3b26a29 100644 --- a/packages/api-plugin-fulfillment/README.md +++ b/packages/api-plugin-fulfillment/README.md @@ -1,68 +1,5 @@ # api-plugin-fulfillment -[![npm (scoped)](https://img.shields.io/npm/v/@reactioncommerce/api-plugin-fulfillment.svg)](https://www.npmjs.com/package/@reactioncommerce/api-plugin-fulfillment) -[![CircleCI](https://circleci.com/gh/reactioncommerce/api-plugin-fulfillment.svg?style=svg)](https://circleci.com/gh/reactioncommerce/api-plugin-fulfillment) - - -## Summary - -This plugin provides the base for all fulfillment types and the fulfillment methods under each type. - -## Included in this fulfillment plugin -### `src/` - -The `src` folder is where all the plugin files resides. - -### `.gitignore` - -A basic `gitignore` file -### `babel.config.cjs` - -If your plugin includes linting and tests, this file is required to allow esmodules to run correctly. - -### `jest.config.cjs` - -If your plugin includes tests, this file is required to allow esmodules to run correctly. You'll need to update the `transformIgnorePatterns` and `moduleNameMapper` sections to include any esmodule `npm` packages used in your plugin. - -### `License.md` - -If your plugin uses `Apache 2` licensing, you can leave this file as-is. If another type of licensing is used, you need to update this file, and the README, accordingly. - -### `package.json` - -The provided `package.json` is set up to install all needed packages and config for linting, testing, and semantic-release. You'll need to update the `name`, `description`, and add any new dependencies your plugin files use. - -### `index.js` - -The entrypoint file for your npm package, will most likely just export your plugin registration from the `src` folder. - -## Developer Certificate of Origin -We use the [Developer Certificate of Origin (DCO)](https://developercertificate.org/) in lieu of a Contributor License Agreement for all contributions to Reaction Commerce open source projects. We request that contributors agree to the terms of the DCO and indicate that agreement by signing all commits made to Reaction Commerce projects by adding a line with your name and email address to every Git commit message contributed: -``` -Signed-off-by: Jane Doe -``` - -You can sign your commit automatically with Git by using `git commit -s` if you have your `user.name` and `user.email` set as part of your Git configuration. - -We ask that you use your real name (please no anonymous contributions or pseudonyms). By signing your commit you are certifying that you have the right have the right to submit it under the open source license used by that particular Reaction Commerce project. You must use your real name (no pseudonyms or anonymous contributions are allowed.) - -We use the [Probot DCO GitHub app](https://github.com/apps/dco) to check for DCO signoffs of every commit. - -If you forget to sign your commits, the DCO bot will remind you and give you detailed instructions for how to amend your commits to add a signature. - -## License - - Copyright 2020 Reaction Commerce - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +This is the base fulfillment plugin which would enable other fulfillment types (like shipping, pickup, digital) and fulfillment-methods (like shipping, store, download etc) to be introduced via their own separate plugins. This plugin provides the common functionalities required for implementing fulfillment. +Each of the newly introduced fulfillment type plugin would need to have the specific fulfillment methods also to be added as separate plugins. Example, fulfillment type 'pickup' could have fulfillment methods like 'store pickup' and 'curb-side pickup' and each of them would be separate plugins. \ No newline at end of file diff --git a/packages/api-plugin-fulfillment/src/index.js b/packages/api-plugin-fulfillment/src/index.js index 2a006b76fac..079be3a337b 100644 --- a/packages/api-plugin-fulfillment/src/index.js +++ b/packages/api-plugin-fulfillment/src/index.js @@ -6,7 +6,7 @@ import schemas from "./schemas/index.js"; import startup from "./startup.js"; import preStartup from "./preStartup.js"; import { MethodEmptyData } from "./simpleSchemas.js"; -import { allRegisteredFulfillmentTypes, registerPluginHandlerForFulfillmentTypes } from "./registration.js"; +import { fulfillment, registerPluginHandlerForFulfillmentTypes } from "./registration.js"; const require = createRequire(import.meta.url); const pkg = require("../package.json"); @@ -31,10 +31,8 @@ export default async function register(app) { Fulfillment: { name: "Fulfillment", indexes: [ - // Create indexes. We set specific names for backwards compatibility - // with indexes created by the aldeed:schema-index Meteor package. - [{ name: 1 }, { name: "c2_name" }], - [{ shopId: 1 }, { name: "c2_shopId" }] + [{ name: 1 }], + [{ shopId: 1 }] ] } }, @@ -50,7 +48,7 @@ export default async function register(app) { startup: [startup] }, contextAdditions: { - allRegisteredFulfillmentTypes + fulfillment }, simpleSchemas: { MethodEmptyData diff --git a/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.js b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.js index b0985d03a74..abd769e76b2 100644 --- a/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.js +++ b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.js @@ -13,7 +13,16 @@ const inputSchema = new SimpleSchema({ * @method createFulfillmentMethodMutation * @summary Creates a fulfillment method * @param {Object} context - an object containing the per-request state - * @param {Object} input - Input (see SimpleSchema) + * @param {Object} input - Input + * @param {Number} cost - optional cost + * @param {String[]} fulfillmentTypes - fulfillment type retained for backward compatibility + * @param {String} group - Group to which fulfillment method belong + * @param {Number} handling - handling charges + * @param {Boolean} enabled - status of fulfillment method + * @param {String} label - displayed on the UI + * @param {String} fulfillmentMethod - used by application, not user editable + * @param {String} displayMessageMethod - used to display additional message on UI specific to ff-method + * @param {Number} rate - ratefor themethod * @returns {Promise} An object with a `method` property containing the created method */ export default async function createFulfillmentMethodMutation(context, input) { @@ -33,15 +42,11 @@ export default async function createFulfillmentMethodMutation(context, input) { const ffTypeMethodRecord = await Fulfillment.findOne({ _id: fulfillmentTypeId, shopId, - methods: { $elemMatch: { name: method.name, fulfillmentMethod: method.fulfillmentMethod } } + methods: { $elemMatch: { fulfillmentMethod: method.fulfillmentMethod } } }); if (ffTypeMethodRecord) throw new ReactionError("server-error", "Fulfillment Method already exists"); method._id = Random.id(); - // MongoDB schema still uses `enabled` rather than `isEnabled` - // method.enabled = method.isEnabled; - // delete method.isEnabled; - method.fulfillmentTypes = [ffTypeRecord.fulfillmentType]; const { matchedCount } = await Fulfillment.updateOne({ diff --git a/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.js b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.js index 50f92af8c19..c0f8ffff0c6 100644 --- a/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.js +++ b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.js @@ -1,16 +1,22 @@ import Random from "@reactioncommerce/random"; import ReactionError from "@reactioncommerce/reaction-error"; -import { fulfillmentTypeSchema } from "../simpleSchemas.js"; +import { FulfillmentTypeSchema } from "../simpleSchemas.js"; /** * @method createFulfillmentType - * @summary updates the selected fulfillment type + * @summary Creates a new fulfillment type * @param {Object} context - an object containing the per-request state - * @param {Object} input - Input (see SimpleSchema) + * @param {Object} input - Input object + * @param {String} input.name - name of fulfillment type + * @param {String} input.shopId - Shop Id + * @param {ProviderSchema} input.provider - Provider details + * @param {String} input.fulfillmentType - fulfillment type + * @param {String} input.displayMessageType - displayMessage for fulfillment type + * @param {FulfillmentMethodSchema[]} input.methods - ff-method array * @returns {Promise} An object with the updated type */ export default async function createFulfillmentType(context, input) { - const cleanedInput = fulfillmentTypeSchema.clean(input); + const cleanedInput = FulfillmentTypeSchema.clean(input); if (!cleanedInput.provider) cleanedInput.provider = {}; cleanedInput.provider.name = cleanedInput.name; @@ -18,13 +24,12 @@ export default async function createFulfillmentType(context, input) { cleanedInput.method._id = Random.id(); cleanedInput.method.fulfillmentTypes = [cleanedInput.fulfillmentType]; } - fulfillmentTypeSchema.validate(cleanedInput); + FulfillmentTypeSchema.validate(cleanedInput); - const { collections } = context; - const { Fulfillment } = collections; - const { shopId, name, fulfillmentType } = cleanedInput; + const { collections: { Fulfillment } } = context; + const { shopId, fulfillmentType } = cleanedInput; - const ffTypeRecord = await Fulfillment.findOne({ name, shopId, fulfillmentType }); + const ffTypeRecord = await Fulfillment.findOne({ shopId, fulfillmentType }); if (ffTypeRecord) throw new ReactionError("invalid-parameter", "Fulfillment Type already exists"); await context.validatePermissions("reaction:legacy:fulfillmentTypes", "create", { shopId }); diff --git a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.js b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.js index 152c7a215f1..45308082112 100644 --- a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.js +++ b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.js @@ -11,9 +11,13 @@ const inputSchema = new SimpleSchema({ /** * @method updateFulfillmentMethodMutation - * @summary updates a flat rate fulfillment method + * @summary updates Fulfillment method * @param {Object} context - an object containing the per-request state - * @param {Object} input - Input (see SimpleSchema) + * @param {Object} input - Input object + * @param {FulfillmentMethodSchema} input.method - fulfillment method object + * @param {String} input.fulfillmentTypeId - id of fulfillment type + * @param {String} input.methodId - ff-method Id + * @param {String} input.shopId - Shop Id * @returns {Promise} An object with a `method` property containing the updated method */ export default async function updateFulfillmentMethodMutation(context, input) { @@ -21,8 +25,7 @@ export default async function updateFulfillmentMethodMutation(context, input) { inputSchema.validate(cleanedInput); const { method: inputMethod, methodId, fulfillmentTypeId, shopId } = cleanedInput; - const { collections } = context; - const { Fulfillment } = collections; + const { collections: { Fulfillment } } = context; const method = { ...inputMethod }; if (!fulfillmentTypeId) throw new ReactionError("invalid-parameter", "Fulfillment Type ID to be updated not provided"); @@ -30,10 +33,6 @@ export default async function updateFulfillmentMethodMutation(context, input) { await context.validatePermissions(`reaction:legacy:fulfillmentMethods:${methodId}`, "update", { shopId }); - // MongoDB schema still uses `enabled` rather than `isEnabled` - // method.enabled = method.isEnabled; - // delete method.isEnabled; - const ffTypeMethodRecord = await Fulfillment.findOne({ "_id": fulfillmentTypeId, shopId, diff --git a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.js b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.js index ada20aa6637..d008c1a3e5c 100644 --- a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.js +++ b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.js @@ -13,7 +13,8 @@ const inputSchema = new SimpleSchema({ }); /** - * @name getShipmentQuotesQueryStatus + * @method getShipmentQuotesQueryStatus + * @summary returns formatted quotes and queryStatus using the rates as input * @param {Array} rates Rate array * @returns {Object} An object with `shipmentQuotes` and `shipmentQuotesQueryStatus` on it * @private diff --git a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.js b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.js index 8282e3dbb00..41c21aef41a 100644 --- a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.js +++ b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.js @@ -20,7 +20,13 @@ const inputSchema = new SimpleSchema({ * @method updateFulfillmentType * @summary updates the selected fulfillment type * @param {Object} context - an object containing the per-request state - * @param {Object} input - Input (see SimpleSchema) + * @param {Object} input - Input object + * @param {String} input.fulfillmentGroupId - fulfillment tpe id of group + * @param {String} input.shopId - Shop Id + * @param {String} input.name - name of fulfillment type + * @param {Boolean} input.enabled - status of ff-type + * @param {String} input.label - label of ff-type + * @param {String} input.displayMessageType - optional display message for UI * @returns {Promise} An object with the updated type */ export default async function updateFulfillmentType(context, input) { diff --git a/packages/api-plugin-fulfillment/src/preStartup.js b/packages/api-plugin-fulfillment/src/preStartup.js index a2f7d44ccf5..e65d3743063 100644 --- a/packages/api-plugin-fulfillment/src/preStartup.js +++ b/packages/api-plugin-fulfillment/src/preStartup.js @@ -10,7 +10,7 @@ const logCtx = { name: "fulfillment", file: "preStartup" }; * @returns {undefined} */ export default async function fulfillmentPreStartup(context) { - const allFulfillmentTypesArray = context.allRegisteredFulfillmentTypes?.registeredFulfillmentTypes; + const allFulfillmentTypesArray = context.fulfillment?.registeredFulfillmentTypes; if (!allFulfillmentTypesArray || allFulfillmentTypesArray.length === 0) { Logger.warn(logCtx, "No fulfillment types available"); diff --git a/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethodsWithQuotes.js b/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethodsWithQuotes.js index dd9759e4d7a..a380cbf744c 100644 --- a/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethodsWithQuotes.js +++ b/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethodsWithQuotes.js @@ -2,7 +2,7 @@ import Logger from "@reactioncommerce/logger"; import ReactionError from "@reactioncommerce/reaction-error"; import extendCommonOrder from "../util/extendCommonOrder.js"; -const logCtx = { name: "fulfillment", file: "getFulfillmentWithQuotes" }; +const logCtx = { name: "fulfillment", file: "getFulfillmentMethodsWithQuotes" }; /** * @name getTitleCaseOfWord diff --git a/packages/api-plugin-fulfillment/src/queries/getFulfillmentType.js b/packages/api-plugin-fulfillment/src/queries/getFulfillmentType.js index c2b0596efc2..81ce44b0276 100644 --- a/packages/api-plugin-fulfillment/src/queries/getFulfillmentType.js +++ b/packages/api-plugin-fulfillment/src/queries/getFulfillmentType.js @@ -7,11 +7,10 @@ * @param {Object} input - Request input * @param {String} input.fulfillmentTypeId - The fulfillment type id * @param {String} input.shopId - The shop id of the fulfillment type - * @returns {Promise} Mongo cursor + * @returns {Promise} Object */ export default async function getFulfillmentType(context, input) { - const { collections } = context; - const { Fulfillment } = collections; + const { collections: { Fulfillment } } = context; const { fulfillmentTypeId, shopId } = input; await context.validatePermissions("reaction:legacy:fulfillmentTypes", "read", { shopId }); diff --git a/packages/api-plugin-fulfillment/src/registration.js b/packages/api-plugin-fulfillment/src/registration.js index 9645f27e9eb..209a52c2b02 100644 --- a/packages/api-plugin-fulfillment/src/registration.js +++ b/packages/api-plugin-fulfillment/src/registration.js @@ -9,7 +9,7 @@ const FulfillmentTypeDeclaration = new SimpleSchema({ } }); -export const allRegisteredFulfillmentTypes = { +export const fulfillment = { registeredFulfillmentTypes: ["undecided"] }; @@ -20,8 +20,8 @@ export const allRegisteredFulfillmentTypes = { */ export function registerPluginHandlerForFulfillmentTypes({ registeredFulfillmentTypes }) { if (registeredFulfillmentTypes) { - allRegisteredFulfillmentTypes.registeredFulfillmentTypes = allRegisteredFulfillmentTypes.registeredFulfillmentTypes.concat(registeredFulfillmentTypes); + fulfillment.registeredFulfillmentTypes = fulfillment.registeredFulfillmentTypes.concat(registeredFulfillmentTypes); } - FulfillmentTypeDeclaration.validate(allRegisteredFulfillmentTypes); + FulfillmentTypeDeclaration.validate(fulfillment); } diff --git a/packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentTypes.js b/packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentTypes.js index f37eb83c2db..588f1c7532a 100644 --- a/packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentTypes.js +++ b/packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentTypes.js @@ -12,7 +12,7 @@ import { decodeShopOpaqueId } from "../../xforms/id.js"; * @param {String} args.shopId - Shop ID to get records for * @param {Object} context - an object containing the per-request state * @param {Object} info Info about the GraphQL request - * @returns {Promise} Fulfillment methods + * @returns {Promise} Fulfillment types under the Shop */ export default async function getFulfillmentTypes(_, args, context, info) { const { shopId: opaqueShopId, ...connectionArgs } = args; diff --git a/packages/api-plugin-fulfillment/src/resolvers/index.js b/packages/api-plugin-fulfillment/src/resolvers/index.js index 63b65a3443d..3fb14c3d6d5 100644 --- a/packages/api-plugin-fulfillment/src/resolvers/index.js +++ b/packages/api-plugin-fulfillment/src/resolvers/index.js @@ -2,11 +2,6 @@ import FulfillmentMethod from "./FulfillmentMethod/index.js"; import Mutation from "./Mutation/index.js"; import Query from "./Query/index.js"; -/** - * Fulfillment related GraphQL resolvers - * @namespace Fulfillment/GraphQL - */ - export default { FulfillmentMethod, Mutation, diff --git a/packages/api-plugin-fulfillment/src/simpleSchemas.js b/packages/api-plugin-fulfillment/src/simpleSchemas.js index 8ecd68298cd..f204edc4c62 100644 --- a/packages/api-plugin-fulfillment/src/simpleSchemas.js +++ b/packages/api-plugin-fulfillment/src/simpleSchemas.js @@ -16,7 +16,22 @@ export const MethodEmptyData = new SimpleSchema({ } }); - +/** + * @name FulfillmentMethodSchema + * @memberof Schemas + * @type {SimpleSchema} + * @summary Defines Schema for Fulfillment Method + * @property {Number} cost - optional cost + * @property {String[]} fulfillmentTypes - fulfillment type retained for backward compatibility + * @property {String} group - Group to which fulfillment method belong + * @property {Number} handling - handling charges + * @property {Boolean} enabled - status of fulfillment method + * @property {String} label - displayed on the UI + * @property {String} name - name of the fulfillment method, user editable + * @property {String} fulfillmentMethod - used by application, not user editable + * @property {String} displayMessageMethod - used to display additional message on UI specific to ff-method + * @property {Number} rate - ratefor themethod + */ export const FulfillmentMethodSchema = new SimpleSchema({ "cost": { type: Number, @@ -43,13 +58,34 @@ export const FulfillmentMethodSchema = new SimpleSchema({ "rate": Number }); +/** + * @name ProviderSchema + * @memberof Schemas + * @type {SimpleSchema} + * @summary Defines Schema for Provider details inside Fulfillment Type + * @property {Boolean} enabled - status of fulfillment method + * @property {String} label - displayed on the UI + * @property {String} name - name of the fulfillment type, user editable + */ const ProviderSchema = new SimpleSchema({ enabled: Boolean, label: String, name: String }); -export const fulfillmentTypeSchema = new SimpleSchema({ +/** + * @name FulfillmentTypeSchema + * @memberof Schemas + * @type {SimpleSchema} + * @summary Defines Schema for Fulfillment Type + * @property {String} name - name of the fulfillment type, user editable + * @property {String} shopId - Shop ID + * @property {ProviderSchema} provider - Provider details + * @property {String} fulfillmentType - fulfillmentType, not user editable + * @property {String} displayMessageType - used to display additional message on UI specific to ff-type + * @property {FulfillmentMethodSchema[]} methods - array of ff-methods under this ff-type + */ +export const FulfillmentTypeSchema = new SimpleSchema({ "name": String, "shopId": String, "provider": { diff --git a/packages/api-plugin-fulfillment/src/startup.js b/packages/api-plugin-fulfillment/src/startup.js index 509d20ea20d..6229e3bd58f 100644 --- a/packages/api-plugin-fulfillment/src/startup.js +++ b/packages/api-plugin-fulfillment/src/startup.js @@ -5,8 +5,7 @@ * @returns {undefined} */ export default async function fulfillmentTypeUndecidedStartup(context) { - const { collections } = context; - const { Fulfillment } = collections; + const { collections: { Fulfillment } } = context; context.appEvents.on("afterShopCreate", async (payload) => { const { shop } = payload; From ebd684c9f57c40ae30aea33d9877423a29096a6a Mon Sep 17 00:00:00 2001 From: Sujith Date: Fri, 28 Oct 2022 00:07:17 +0530 Subject: [PATCH 09/71] fix: removed string concatenation logic Signed-off-by: Sujith --- .../src/mutations/createFulfillmentMethod.js | 3 +-- .../src/queries/getFulfillmentMethods.js | 3 +-- .../getFulfillmentMethodsWithQuotes.js | 21 +++---------------- .../src/queries/getFulfillmentTypes.js | 3 +-- 4 files changed, 6 insertions(+), 24 deletions(-) diff --git a/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.js b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.js index abd769e76b2..0d1226c9ad9 100644 --- a/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.js +++ b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.js @@ -30,8 +30,7 @@ export default async function createFulfillmentMethodMutation(context, input) { inputSchema.validate(cleanedInput); const { method: inputMethod, fulfillmentTypeId, shopId } = cleanedInput; - const { collections } = context; - const { Fulfillment } = collections; + const { collections: { Fulfillment } } = context; const method = { ...inputMethod }; await context.validatePermissions("reaction:legacy:fulfillmentMethods", "create", { shopId }); diff --git a/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethods.js b/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethods.js index 3731ea54832..eedfa584ccb 100644 --- a/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethods.js +++ b/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethods.js @@ -10,8 +10,7 @@ * @returns {Promise} Mongo cursor */ export default async function getFulfillmentMethods(context, input) { - const { collections } = context; - const { Fulfillment } = collections; + const { collections: { Fulfillment } } = context; const { shopId, fulfillmentTypeId } = input; await context.validatePermissions("reaction:legacy:fulfillmentTypes", "read", { shopId }); diff --git a/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethodsWithQuotes.js b/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethodsWithQuotes.js index a380cbf744c..7c3957ef519 100644 --- a/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethodsWithQuotes.js +++ b/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethodsWithQuotes.js @@ -4,21 +4,6 @@ import extendCommonOrder from "../util/extendCommonOrder.js"; const logCtx = { name: "fulfillment", file: "getFulfillmentMethodsWithQuotes" }; -/** - * @name getTitleCaseOfWord - * @method - * @summary converts the given word to Title case - * @param {String} inputWord - input word - * @returns {String} return Title case of word - * @private - */ -function getTitleCaseOfWord(inputWord) { - let outWord = String(inputWord); - outWord = outWord.toLowerCase(); - outWord = outWord.charAt(0).toUpperCase() + outWord.slice(1); - return outWord; -} - /** * @name getFulfillmentMethodsWithQuotes * @method @@ -42,9 +27,9 @@ export default async function getFulfillmentMethodsWithQuotes(commonOrder, conte const fulfillmentTypeInGroup = commonOrder.fulfillmentType; if (!fulfillmentTypeInGroup) throw new ReactionError("not-found", "Fulfillment type not found in commonOrder"); - const fulfillmentTypeTitleCase = getTitleCaseOfWord(fulfillmentTypeInGroup); - const functionTypesToCall = `getFulfillmentMethodsWithQuotes${fulfillmentTypeTitleCase}`; - const funcs = context.getFunctionsOfType(functionTypesToCall); + const allFuncsArray = context.getFunctionsOfType("getFulfillmentMethodsWithQuotes"); + const ffTypeFuncObjects = allFuncsArray.filter((func) => fulfillmentTypeInGroup === func.key); + const funcs = ffTypeFuncObjects.map((func) => func.handler); if (!funcs || !Array.isArray(funcs) || !funcs.length) throw new ReactionError("not-found", `No methods for Fulfillment type ${fulfillmentTypeInGroup}`); diff --git a/packages/api-plugin-fulfillment/src/queries/getFulfillmentTypes.js b/packages/api-plugin-fulfillment/src/queries/getFulfillmentTypes.js index 546d01d672f..ba2e20e3ca9 100644 --- a/packages/api-plugin-fulfillment/src/queries/getFulfillmentTypes.js +++ b/packages/api-plugin-fulfillment/src/queries/getFulfillmentTypes.js @@ -9,8 +9,7 @@ * @returns {Promise} Mongo cursor */ export default async function getFulfillmentTypes(context, input) { - const { collections } = context; - const { Fulfillment } = collections; + const { collections: { Fulfillment } } = context; const { shopId } = input; await context.validatePermissions("reaction:legacy:fulfillmentTypes", "read", { shopId }); From 3ecab7d53a3e7019160aa26adac0c2520ea8d184 Mon Sep 17 00:00:00 2001 From: Sujith Date: Fri, 28 Oct 2022 17:07:28 +0530 Subject: [PATCH 10/71] fix: use queries.getCartById delete /util version Signed-off-by: Sujith --- .../selectFulfillmentOptionForGroup.js | 3 +- .../selectFulfillmentOptionForGroup.test.js | 4 +-- .../updateFulfillmentOptionsForGroup.js | 3 +- .../updateFulfillmentOptionsForGroup.test.js | 4 +-- .../src/util/getCartById.js | 34 ------------------- 5 files changed, 6 insertions(+), 42 deletions(-) delete mode 100644 packages/api-plugin-fulfillment/src/util/getCartById.js diff --git a/packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.js b/packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.js index f6a56024e21..7eb6c054985 100644 --- a/packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.js +++ b/packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.js @@ -1,6 +1,5 @@ import SimpleSchema from "simpl-schema"; import ReactionError from "@reactioncommerce/reaction-error"; -import getCartById from "../util/getCartById.js"; const inputSchema = new SimpleSchema({ cartId: String, @@ -30,7 +29,7 @@ export default async function selectFulfillmentOptionForGroup(context, input) { const { cartId, cartToken, fulfillmentGroupId, fulfillmentMethodId } = cleanedInput; - const cart = await getCartById(context, cartId, { cartToken, throwIfNotFound: true }); + const cart = await context.queries.getCartById(context, cartId, { cartToken, throwIfNotFound: true }); const fulfillmentGroup = (cart.shipping || []).find((group) => group._id === fulfillmentGroupId); if (!fulfillmentGroup) throw new ReactionError("not-found", `Fulfillment group with ID ${fulfillmentGroupId} not found in cart with ID ${cartId}`); diff --git a/packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.test.js b/packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.test.js index 9068349c76d..9b6e362af3a 100644 --- a/packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.test.js +++ b/packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.test.js @@ -1,7 +1,7 @@ import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; import selectFulfillmentOptionForGroup from "./selectFulfillmentOptionForGroup.js"; -jest.mock("../util/getCartById", () => jest.fn().mockImplementation(() => Promise.resolve({ +mockContext.queries.getCartById = jest.fn().mockName("getCartById").mockReturnValueOnce(Promise.resolve({ _id: "cartId", shipping: [{ _id: "group1", @@ -14,7 +14,7 @@ jest.mock("../util/getCartById", () => jest.fn().mockImplementation(() => Promis }], type: "shipping" }] -}))); +})); beforeAll(() => { if (!mockContext.mutations.saveCart) { diff --git a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.js b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.js index d008c1a3e5c..78de9b922e7 100644 --- a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.js +++ b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.js @@ -1,7 +1,6 @@ import _ from "lodash"; import SimpleSchema from "simpl-schema"; import ReactionError from "@reactioncommerce/reaction-error"; -import getCartById from "../util/getCartById.js"; const inputSchema = new SimpleSchema({ cartId: String, @@ -66,7 +65,7 @@ export default async function updateFulfillmentOptionsForGroup(context, input) { const { cartId, cartToken, fulfillmentGroupId } = cleanedInput; - const cart = await getCartById(context, cartId, { cartToken, throwIfNotFound: true }); + const cart = await context.queries.getCartById(context, cartId, { cartToken, throwIfNotFound: true }); // This is done by `saveCart`, too, but we need to do it before every call to `getCommonOrderForCartGroup` // to avoid errors in the case where a product has been deleted since the last time this cart was saved. diff --git a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.test.js b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.test.js index 2dbbc8a9b0f..ce97fd76b18 100644 --- a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.test.js +++ b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.test.js @@ -2,7 +2,7 @@ import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; import Factory from "../tests/factory.js"; import updateFulfillmentOptionsForGroup from "./updateFulfillmentOptionsForGroup.js"; -jest.mock("../util/getCartById", () => jest.fn().mockImplementation(() => Promise.resolve({ +mockContext.queries.getCartById = jest.fn().mockName("getCartById").mockReturnValueOnce(Promise.resolve({ _id: "cartId", items: [{ _id: "123", @@ -21,7 +21,7 @@ jest.mock("../util/getCartById", () => jest.fn().mockImplementation(() => Promis itemIds: ["123"], type: "shipping" }] -}))); +})); const fakeCart = Factory.Cart.makeOne(); const fakeQuote = Factory.ShipmentQuote.makeOne(); diff --git a/packages/api-plugin-fulfillment/src/util/getCartById.js b/packages/api-plugin-fulfillment/src/util/getCartById.js deleted file mode 100644 index 3142f9d4f69..00000000000 --- a/packages/api-plugin-fulfillment/src/util/getCartById.js +++ /dev/null @@ -1,34 +0,0 @@ -import hashToken from "@reactioncommerce/api-utils/hashToken.js"; -import ReactionError from "@reactioncommerce/reaction-error"; - -/** - * @summary Gets a cart from the db by ID. If there is an account for the request, verifies that the - * account has permission to access the cart. Optionally throws an error if not found. - * @param {Object} context Object defining the request state - * @param {String} cartId The cart ID - * @param {Object} [options] Options - * @param {String} [options.cartToken] Cart token, required if it's an anonymous cart - * @param {Boolean} [options.throwIfNotFound] Default false. Throw a not-found error rather than return null `cart` - * @returns {Object|null} The cart document, or null if not found and `throwIfNotFound` was false - */ -export default async function getCartById(context, cartId, { cartToken, throwIfNotFound = false } = {}) { - const { accountId, collections } = context; - const { Cart } = collections; - - const selector = { _id: cartId }; - - if (cartToken) { - selector.anonymousAccessToken = hashToken(cartToken); - } - - const cart = await Cart.findOne(selector); - if (!cart && throwIfNotFound) { - throw new ReactionError("not-found", "Cart not found"); - } - - if (cart && cart.accountId && cart.accountId !== accountId) { - throw new ReactionError("access-denied", "Access Denied"); - } - - return cart || null; -} From fd9d72cd1ef5ba370ecb2d884468760c0b708ad1 Mon Sep 17 00:00:00 2001 From: Sujith Date: Tue, 1 Nov 2022 00:12:04 +0530 Subject: [PATCH 11/71] fix: fixed broken test Signed-off-by: Sujith --- .../selectFulfillmentOptionForGroup.test.js | 30 +++++++------- .../updateFulfillmentOptionsForGroup.test.js | 41 ++++++++++--------- 2 files changed, 37 insertions(+), 34 deletions(-) diff --git a/packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.test.js b/packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.test.js index 9b6e362af3a..a526864913d 100644 --- a/packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.test.js +++ b/packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.test.js @@ -1,20 +1,22 @@ import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; import selectFulfillmentOptionForGroup from "./selectFulfillmentOptionForGroup.js"; -mockContext.queries.getCartById = jest.fn().mockName("getCartById").mockReturnValueOnce(Promise.resolve({ - _id: "cartId", - shipping: [{ - _id: "group1", - itemIds: ["123"], - shipmentQuotes: [{ - rate: 0, - method: { - _id: "valid-method" - } - }], - type: "shipping" - }] -})); +beforeEach(() => { + mockContext.queries.getCartById = jest.fn().mockName("getCartById").mockReturnValueOnce(Promise.resolve({ + _id: "cartId", + shipping: [{ + _id: "group1", + itemIds: ["123"], + shipmentQuotes: [{ + rate: 0, + method: { + _id: "valid-method" + } + }], + type: "shipping" + }] + })); +}); beforeAll(() => { if (!mockContext.mutations.saveCart) { diff --git a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.test.js b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.test.js index ce97fd76b18..557637fa626 100644 --- a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.test.js +++ b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.test.js @@ -2,26 +2,6 @@ import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; import Factory from "../tests/factory.js"; import updateFulfillmentOptionsForGroup from "./updateFulfillmentOptionsForGroup.js"; -mockContext.queries.getCartById = jest.fn().mockName("getCartById").mockReturnValueOnce(Promise.resolve({ - _id: "cartId", - items: [{ - _id: "123", - price: { - amount: 19.99 - }, - priceWhenAdded: { - amount: 19.99 - }, - subtotal: { - amount: 19.99 - } - }], - shipping: [{ - _id: "group1", - itemIds: ["123"], - type: "shipping" - }] -})); const fakeCart = Factory.Cart.makeOne(); const fakeQuote = Factory.ShipmentQuote.makeOne(); @@ -43,6 +23,27 @@ beforeAll(() => { beforeEach(() => { mockGetFulfillmentMethodsWithQuotes.mockClear(); + + mockContext.queries.getCartById = jest.fn().mockName("getCartById").mockReturnValueOnce(Promise.resolve({ + _id: "cartId", + items: [{ + _id: "123", + price: { + amount: 19.99 + }, + priceWhenAdded: { + amount: 19.99 + }, + subtotal: { + amount: 19.99 + } + }], + shipping: [{ + _id: "group1", + itemIds: ["123"], + type: "shipping" + }] + })); }); test("updates cart properly for empty rates", async () => { From 5a09c25b1c91700adeccf04dd1a01b89c6db1f65 Mon Sep 17 00:00:00 2001 From: Sujith Date: Wed, 19 Oct 2022 23:03:11 +0530 Subject: [PATCH 12/71] feat: fulfillment type pickup and method store Signed-off-by: Sujith --- .../.gitignore | 61 ++++++ .../LICENSE | 201 ++++++++++++++++++ .../README.md | 70 ++++++ .../babel.config.cjs | 1 + .../index.js | 3 + .../jest.config.cjs | 1 + .../package.json | 44 ++++ ...FulfillmentMethodsWithQuotesPickupStore.js | 77 +++++++ ...llmentMethodsWithQuotesPickupStore.test.js | 128 +++++++++++ .../src/index.js | 36 ++++ .../src/preStartup.js | 27 +++ .../src/schemas/index.js | 5 + .../src/schemas/schema.graphql | 14 ++ .../src/simpleSchemas.js | 45 ++++ .../src/startup.js | 22 ++ ...eckAndCreateFulfillmentMethod.test.js.snap | 3 + .../util/checkAndCreateFulfillmentMethod.js | 31 +++ .../checkAndCreateFulfillmentMethod.test.js | 89 ++++++++ .../src/util/collectStoreDetails.js | 48 +++++ .../src/util/validateOrderMethodsstore.js | 22 ++ .../.gitignore | 61 ++++++ .../LICENSE | 201 ++++++++++++++++++ .../README.md | 72 +++++++ .../babel.config.cjs | 1 + .../index.js | 3 + .../jest.config.cjs | 1 + .../package.json | 42 ++++ .../src/checkAndCreateFulfillmentType.js | 26 +++ .../src/checkAndCreateFulfillmentType.test.js | 61 ++++++ .../src/index.js | 26 +++ .../src/schemas/index.js | 5 + .../src/schemas/schema.graphql | 5 + .../src/startup.js | 22 ++ 33 files changed, 1454 insertions(+) create mode 100644 packages/api-plugin-fulfillment-method-pickup-store/.gitignore create mode 100644 packages/api-plugin-fulfillment-method-pickup-store/LICENSE create mode 100644 packages/api-plugin-fulfillment-method-pickup-store/README.md create mode 100644 packages/api-plugin-fulfillment-method-pickup-store/babel.config.cjs create mode 100644 packages/api-plugin-fulfillment-method-pickup-store/index.js create mode 100644 packages/api-plugin-fulfillment-method-pickup-store/jest.config.cjs create mode 100644 packages/api-plugin-fulfillment-method-pickup-store/package.json create mode 100644 packages/api-plugin-fulfillment-method-pickup-store/src/getFulfillmentMethodsWithQuotesPickupStore.js create mode 100644 packages/api-plugin-fulfillment-method-pickup-store/src/getFulfillmentMethodsWithQuotesPickupStore.test.js create mode 100644 packages/api-plugin-fulfillment-method-pickup-store/src/index.js create mode 100644 packages/api-plugin-fulfillment-method-pickup-store/src/preStartup.js create mode 100644 packages/api-plugin-fulfillment-method-pickup-store/src/schemas/index.js create mode 100644 packages/api-plugin-fulfillment-method-pickup-store/src/schemas/schema.graphql create mode 100644 packages/api-plugin-fulfillment-method-pickup-store/src/simpleSchemas.js create mode 100644 packages/api-plugin-fulfillment-method-pickup-store/src/startup.js create mode 100644 packages/api-plugin-fulfillment-method-pickup-store/src/util/__snapshots__/checkAndCreateFulfillmentMethod.test.js.snap create mode 100644 packages/api-plugin-fulfillment-method-pickup-store/src/util/checkAndCreateFulfillmentMethod.js create mode 100644 packages/api-plugin-fulfillment-method-pickup-store/src/util/checkAndCreateFulfillmentMethod.test.js create mode 100644 packages/api-plugin-fulfillment-method-pickup-store/src/util/collectStoreDetails.js create mode 100644 packages/api-plugin-fulfillment-method-pickup-store/src/util/validateOrderMethodsstore.js create mode 100644 packages/api-plugin-fulfillment-type-pickup/.gitignore create mode 100644 packages/api-plugin-fulfillment-type-pickup/LICENSE create mode 100644 packages/api-plugin-fulfillment-type-pickup/README.md create mode 100644 packages/api-plugin-fulfillment-type-pickup/babel.config.cjs create mode 100644 packages/api-plugin-fulfillment-type-pickup/index.js create mode 100644 packages/api-plugin-fulfillment-type-pickup/jest.config.cjs create mode 100644 packages/api-plugin-fulfillment-type-pickup/package.json create mode 100644 packages/api-plugin-fulfillment-type-pickup/src/checkAndCreateFulfillmentType.js create mode 100644 packages/api-plugin-fulfillment-type-pickup/src/checkAndCreateFulfillmentType.test.js create mode 100644 packages/api-plugin-fulfillment-type-pickup/src/index.js create mode 100644 packages/api-plugin-fulfillment-type-pickup/src/schemas/index.js create mode 100644 packages/api-plugin-fulfillment-type-pickup/src/schemas/schema.graphql create mode 100644 packages/api-plugin-fulfillment-type-pickup/src/startup.js diff --git a/packages/api-plugin-fulfillment-method-pickup-store/.gitignore b/packages/api-plugin-fulfillment-method-pickup-store/.gitignore new file mode 100644 index 00000000000..ad46b30886f --- /dev/null +++ b/packages/api-plugin-fulfillment-method-pickup-store/.gitignore @@ -0,0 +1,61 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# next.js build output +.next diff --git a/packages/api-plugin-fulfillment-method-pickup-store/LICENSE b/packages/api-plugin-fulfillment-method-pickup-store/LICENSE new file mode 100644 index 00000000000..261eeb9e9f8 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-pickup-store/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/api-plugin-fulfillment-method-pickup-store/README.md b/packages/api-plugin-fulfillment-method-pickup-store/README.md new file mode 100644 index 00000000000..447b4df70a0 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-pickup-store/README.md @@ -0,0 +1,70 @@ +# api-plugin-fulfillment-method-pickup-store + +[![npm (scoped)](https://img.shields.io/npm/v/@reactioncommerce/api-plugin-fulfillment-method-pickup-store.svg)](https://www.npmjs.com/package/@reactioncommerce/api-plugin-fulfillment-method-pickup-store) +[![CircleCI](https://circleci.com/gh/reactioncommerce/api-plugin-fulfillment-method-pickup-store.svg?style=svg)](https://circleci.com/gh/reactioncommerce/api-plugin-fulfillment-method-pickup-store) + + +## Summary + +This plugin Implements Store pickup as a fulfillment method under the type pickup. + +## Included in this fulfillment-method-pickup-store plugin +ß +### `src/` + +The `src` folder contains all the plugin files. + +### `.gitignore` + +A basic `gitignore` file + +### `babel.config.cjs` + +If your plugin includes linting and tests, this file is required to allow esmodules to run correctly. + +### `jest.config.cjs` + +If your plugin includes tests, this file is required to allow esmodules to run correctly. You'll need to update the `transformIgnorePatterns` and `moduleNameMapper` sections to include any esmodule `npm` packages used in your plugin. + +### `License.md` + +If your plugin uses `Apache 2` licensing, you can leave this file as-is. If another type of licensing is used, you need to update this file, and the README, accordingly. + +### `package.json` + +The provided `package.json` is set up to install all needed packages and config for linting, testing, and semantic-release. You'll need to update the `name`, `description`, and add any new dependencies your plugin files use. + +### `index.js` + +The entrypoint file for your npm package, will most likely just export your plugin registration from the `src` folder. + +## Developer Certificate of Origin +We use the [Developer Certificate of Origin (DCO)](https://developercertificate.org/) in lieu of a Contributor License Agreement for all contributions to Reaction Commerce open source projects. We request that contributors agree to the terms of the DCO and indicate that agreement by signing all commits made to Reaction Commerce projects by adding a line with your name and email address to every Git commit message contributed: +``` +Signed-off-by: Jane Doe +``` + +You can sign your commit automatically with Git by using `git commit -s` if you have your `user.name` and `user.email` set as part of your Git configuration. + +We ask that you use your real name (please no anonymous contributions or pseudonyms). By signing your commit you are certifying that you have the right have the right to submit it under the open source license used by that particular Reaction Commerce project. You must use your real name (no pseudonyms or anonymous contributions are allowed.) + +We use the [Probot DCO GitHub app](https://github.com/apps/dco) to check for DCO signoffs of every commit. + +If you forget to sign your commits, the DCO bot will remind you and give you detailed instructions for how to amend your commits to add a signature. + +## License + + Copyright 2020 Reaction Commerce + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/packages/api-plugin-fulfillment-method-pickup-store/babel.config.cjs b/packages/api-plugin-fulfillment-method-pickup-store/babel.config.cjs new file mode 100644 index 00000000000..5fa924c0809 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-pickup-store/babel.config.cjs @@ -0,0 +1 @@ +module.exports = require("@reactioncommerce/api-utils/lib/configs/babel.config.cjs"); diff --git a/packages/api-plugin-fulfillment-method-pickup-store/index.js b/packages/api-plugin-fulfillment-method-pickup-store/index.js new file mode 100644 index 00000000000..d7ea8b28c59 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-pickup-store/index.js @@ -0,0 +1,3 @@ +import register from "./src/index.js"; + +export default register; diff --git a/packages/api-plugin-fulfillment-method-pickup-store/jest.config.cjs b/packages/api-plugin-fulfillment-method-pickup-store/jest.config.cjs new file mode 100644 index 00000000000..2bdefefceb9 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-pickup-store/jest.config.cjs @@ -0,0 +1 @@ +module.exports = require("@reactioncommerce/api-utils/lib/configs/jest.config.cjs"); diff --git a/packages/api-plugin-fulfillment-method-pickup-store/package.json b/packages/api-plugin-fulfillment-method-pickup-store/package.json new file mode 100644 index 00000000000..22d15f80d78 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-pickup-store/package.json @@ -0,0 +1,44 @@ +{ + "name": "@reactioncommerce/api-plugin-fulfillment-method-pickup-store", + "description": "Implements Store-pickup as a fulfillment method under the type pickup", + "label": "Fulfillment Method Store Pickup", + "version": "1.0.0", + "main": "index.js", + "type": "module", + "engines": { + "node": ">=14.18.1", + "npm": ">=7" + }, + "homepage": "https://github.com/reactioncommerce/reaction", + "url": "https://github.com/reactioncommerce/reaction", + "email": "hello-open-commerce@mailchimp.com", + "repository": { + "type": "git", + "url": "git+https://github.com/reactioncommerce/reaction.git", + "directory": "packages/api-plugin-fulfillment-method-pickup-store" + }, + "author": { + "name": "Mailchimp Open Commerce", + "email": "hello-open-commerce@mailchimp.com", + "url": "https://mailchimp.com/developer/open-commerce/" + }, + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/reactioncommerce/reaction/issues" + }, + "sideEffects": false, + "dependencies": { + "simpl-schema": "^1.12.2", + "@reactioncommerce/logger": "^1.1.4", + "@reactioncommerce/reaction-error": "^1.0.1", + "@reactioncommerce/api-utils": "^1.16.9" + }, + "devDependencies": {}, + "scripts": { + "lint": "npm run lint:eslint", + "lint:eslint": "eslint .", + "test": "jest", + "test:watch": "jest --watch", + "test:file": "jest --no-cache --watch --coverage=false" + } +} diff --git a/packages/api-plugin-fulfillment-method-pickup-store/src/getFulfillmentMethodsWithQuotesPickupStore.js b/packages/api-plugin-fulfillment-method-pickup-store/src/getFulfillmentMethodsWithQuotesPickupStore.js new file mode 100644 index 00000000000..e3be3ee3658 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-pickup-store/src/getFulfillmentMethodsWithQuotesPickupStore.js @@ -0,0 +1,77 @@ +import Logger from "@reactioncommerce/logger"; +import collectStoreDetails from "./util/collectStoreDetails.js"; + +const packageName = "fulfillment-method-pickup-store"; +const fulfillmentTypeName = "pickup"; +const fulfillmentMethodName = "store"; +const logCtx = { name: "fulfillment-method-pickup-store", file: "getFulfillmentMethodsWithQuotesPickupStore" }; + +/** + * @summary Returns a list of fulfillment method quotes based on the items in a fulfillment group. + * @param {Object} context - Context + * @param {Object} commonOrder - details about the purchase a user wants to make. + * @param {Array} [previousQueryResults] - an array of shipping rates and + * info about failed calls to the APIs of some shipping methods providers + * @returns {Array} - an array that contains two arrays: the first array will + * be an updated list of shipping rates, and the second will contain info for + * retrying this specific package if any errors occurred while retrieving the + * shipping rates. + * @private + */ +export default async function getFulfillmentMethodsWithQuotesPickupStore(context, commonOrder, previousQueryResults = []) { + const { collections } = context; + const { Fulfillment } = collections; + const [rates = [], retrialTargets = []] = previousQueryResults; + const currentMethodInfo = { packageName }; + + if (retrialTargets.length > 0) { + const isNotAmongFailedRequests = retrialTargets.every((target) => target.packageName !== packageName); + if (isNotAmongFailedRequests) { + return previousQueryResults; + } + } + + const pickupDocs = await Fulfillment.find({ + "shopId": commonOrder.shopId, + "fulfillmentType": fulfillmentTypeName, + "provider.enabled": true + }).toArray(); + if (!pickupDocs || !pickupDocs.length) { + return [rates, retrialTargets]; + } + + const initialNumOfRates = rates.length; + + const awaitedPickupDocs = pickupDocs.map(async (doc) => { + const carrier = doc.provider.label; + const currentPluginMethods = doc.methods.filter((method) => ((method.name === fulfillmentMethodName) && (method.enabled))); + + for (const method of currentPluginMethods) { + const updatedMethod = collectStoreDetails(method, commonOrder); + + rates.push({ + carrier, + handlingPrice: updatedMethod.handling, + method: updatedMethod, + rate: updatedMethod.rate, + shippingPrice: updatedMethod.rate + updatedMethod.handling, + shopId: doc.shopId + }); + } + }); + await Promise.all(awaitedPickupDocs); + + if (rates.length === initialNumOfRates) { + const errorDetails = { + requestStatus: "error", + shippingProvider: packageName, + message: "Pickup Store did not return any pickup methods." + }; + rates.push(errorDetails); + retrialTargets.push(currentMethodInfo); + return [rates, retrialTargets]; + } + + Logger.debug({ ...logCtx, rates }, "Store getFulfillmentMethodsWithQuotesPickupStore"); + return [rates, retrialTargets]; +} diff --git a/packages/api-plugin-fulfillment-method-pickup-store/src/getFulfillmentMethodsWithQuotesPickupStore.test.js b/packages/api-plugin-fulfillment-method-pickup-store/src/getFulfillmentMethodsWithQuotesPickupStore.test.js new file mode 100644 index 00000000000..a9c378d8bfb --- /dev/null +++ b/packages/api-plugin-fulfillment-method-pickup-store/src/getFulfillmentMethodsWithQuotesPickupStore.test.js @@ -0,0 +1,128 @@ +import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; +import getFulfillmentMethodsWithQuotesPickupStore from "./getFulfillmentMethodsWithQuotesPickupStore.js"; + +test("should return previousResults if pickup is not among FailedRequests", async () => { + const commonOrder = { + _id: "order123" + }; + const previousResults = [ + [], [ + { + packageName: "some-other-fulfillment-method" + } + ] + ]; + const result = await getFulfillmentMethodsWithQuotesPickupStore(mockContext, commonOrder, previousResults); + expect(result).toEqual(previousResults); +}); + + +test("should return previousResults if not fulfillment records enabled", async () => { + const previousResults = [ + [ + { + carrier: "carrier123", + handlingPrice: 99, + rate: 99, + shippingPrice: 198, + shopId: "SHOP_ID" + } + ], [] + ]; + const commonOrder = { + _id: "order123" + }; + + mockContext.collections.Fulfillment = { + find: jest.fn(() => ({ toArray: () => [] })) + }; + const result = await getFulfillmentMethodsWithQuotesPickupStore(mockContext, commonOrder, previousResults); + expect(result).toEqual(previousResults); +}); + + +test("should return rates witout error", async () => { + const previousResults = [ + [ + { + carrier: "Pickup", + handlingPrice: 10, + rate: 5, + shippingPrice: 15, + shopId: "SHOP_ID" + } + ], [] + ]; + const commonOrder = { + _id: "order123" + }; + const pickupDoc = { + _id: "fulfillment123", + name: "Default Pickup Provider", + shopId: "SHOP_ID", + provider: { + enabled: true, + label: "Pickup", + name: "pickup" + }, + fulfillmentType: "pickup", + methods: [{ + shopId: "SHOP_ID", + cost: 99, + handling: 99, + rate: 99, + fulfillmentTypes: ["pickup"], + group: "Ground", + enabled: true, + label: "Store", + name: "store", + fulfillmentMethod: "store", + displayMessageMethod: "Sample display message" + }] + }; + const expectedNewRate = { + carrier: "Pickup", + handlingPrice: 10, + method: { + shopId: "SHOP_ID", + cost: 99, + handling: 10, + rate: 5, + fulfillmentTypes: [ + "pickup" + ], + group: "Ground", + enabled: true, + label: "Store", + name: "store", + fulfillmentMethod: "store", + displayMessageMethod: "Sample display message", + carrier: "Store", + methodAdditionalData: { + gqlType: "storeData", + storeData: [ + { + storeId: "Store-1", + storeAddress: "123, 5th Main, Some place", + storeTiming: "7am to 9pm" + }, + { + storeId: "Store-2", + storeAddress: "456, 50th Main, Some other place", + storeTiming: "7am to 9pm" + } + ] + } + }, + rate: 5, + shippingPrice: 15, + shopId: "SHOP_ID" + }; + const expectedResult = [[...previousResults[0], expectedNewRate], []]; + + mockContext.collections.Fulfillment = { + find: jest.fn(() => ({ toArray: () => [pickupDoc] })) + }; + const result = await getFulfillmentMethodsWithQuotesPickupStore(mockContext, commonOrder, previousResults); + expect(result).toEqual(expectedResult); +}); diff --git a/packages/api-plugin-fulfillment-method-pickup-store/src/index.js b/packages/api-plugin-fulfillment-method-pickup-store/src/index.js new file mode 100644 index 00000000000..d43775c514b --- /dev/null +++ b/packages/api-plugin-fulfillment-method-pickup-store/src/index.js @@ -0,0 +1,36 @@ +import { createRequire } from "module"; +import fulfillmentMethodPickupStorePreStartup from "./preStartup.js"; +import fulfillmentMethodPickupStoreStartup from "./startup.js"; +import { MethodStoreData } from "./simpleSchemas.js"; +import schemas from "./schemas/index.js"; +import getFulfillmentMethodsWithQuotesPickupStore from "./getFulfillmentMethodsWithQuotesPickupStore.js"; +import validateOrderMethodsstore from "./util/validateOrderMethodsstore.js"; + +const require = createRequire(import.meta.url); +const pkg = require("../package.json"); + +/** + * @summary Import and call this function to add this plugin to your API. + * @param {Object} app The ReactionAPI instance + * @returns {undefined} + */ +export default async function register(app) { + await app.registerPlugin({ + label: "Fulfillment Method Pickup Store", + name: "fulfillment-method-pickup-store", + version: pkg.version, + graphQL: { + schemas + }, + simpleSchemas: { + MethodStoreData + }, + functionsByType: { + preStartup: [fulfillmentMethodPickupStorePreStartup], + startup: [fulfillmentMethodPickupStoreStartup], + validateOrderMethods: [validateOrderMethodsstore], + getFulfillmentMethodsWithQuotes: [getFulfillmentMethodsWithQuotesPickupStore], + getFulfillmentMethodsWithQuotesPickup: [getFulfillmentMethodsWithQuotesPickupStore] + } + }); +} diff --git a/packages/api-plugin-fulfillment-method-pickup-store/src/preStartup.js b/packages/api-plugin-fulfillment-method-pickup-store/src/preStartup.js new file mode 100644 index 00000000000..a0773a8d02a --- /dev/null +++ b/packages/api-plugin-fulfillment-method-pickup-store/src/preStartup.js @@ -0,0 +1,27 @@ +import { MethodStoreData } from "./simpleSchemas.js"; +/** + * @summary Called on preStartup + * @param {Object} context Startup context + * @returns {undefined} + */ +export default async function fulfillmentMethodPickupStorePreStartup(context) { + const { simpleSchemas: { ShippingMethod, SelectedFulfillmentOption } } = context; + + ShippingMethod.extend({ + methodAdditionalData: { + type: ShippingMethod.getDefinition( + "methodAdditionalData", + ["type"] + ).type[0].type.extend(MethodStoreData) + } + }); + + SelectedFulfillmentOption.extend({ + methodAdditionalData: { + type: SelectedFulfillmentOption.getDefinition( + "methodAdditionalData", + ["type"] + ).type[0].type.extend(MethodStoreData) + } + }); +} diff --git a/packages/api-plugin-fulfillment-method-pickup-store/src/schemas/index.js b/packages/api-plugin-fulfillment-method-pickup-store/src/schemas/index.js new file mode 100644 index 00000000000..30096f92e54 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-pickup-store/src/schemas/index.js @@ -0,0 +1,5 @@ +import importAsString from "@reactioncommerce/api-utils/importAsString.js"; + +const schema = importAsString("./schema.graphql"); + +export default [schema]; diff --git a/packages/api-plugin-fulfillment-method-pickup-store/src/schemas/schema.graphql b/packages/api-plugin-fulfillment-method-pickup-store/src/schemas/schema.graphql new file mode 100644 index 00000000000..fccb208a807 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-pickup-store/src/schemas/schema.graphql @@ -0,0 +1,14 @@ +"Additional data fields from Store" +type storeFields { + storeId: String + storeAddress: String + storeTiming: String +} + +"Additional Store data - Pickup" +type storeData { + gqlType: String + storeData: [storeFields] +} + +extend union AdditionalData = storeData diff --git a/packages/api-plugin-fulfillment-method-pickup-store/src/simpleSchemas.js b/packages/api-plugin-fulfillment-method-pickup-store/src/simpleSchemas.js new file mode 100644 index 00000000000..c7140eda9af --- /dev/null +++ b/packages/api-plugin-fulfillment-method-pickup-store/src/simpleSchemas.js @@ -0,0 +1,45 @@ +import SimpleSchema from "simpl-schema"; + +/** + * @name StoreFields + * @memberof Schemas + * @type {SimpleSchema} + * @summary Specific fields returned by the Store Pickup fulfillment method. + * @property {String} storeId Store ID + * @property {String} storeAddress Store Address + * @property {String} storeTiming Store Timing + */ +const StoreFields = new SimpleSchema({ + storeId: { + type: String, + optional: true + }, + storeAddress: { + type: String, + optional: true + }, + storeTiming: { + type: String, + optional: true + } +}); + +/** + * @name MethodStoreData + * @memberof Schemas + * @type {SimpleSchema} + * @summary Defines an array of Store fields + * @property {String} gqlType Defines the method type + * @property {StoreFields[]} storeData Store Data fields + */ +export const MethodStoreData = new SimpleSchema({ + "gqlType": String, + "storeData": { + type: Array, + optional: true + }, + "storeData.$": { + type: StoreFields, + optional: true + } +}); diff --git a/packages/api-plugin-fulfillment-method-pickup-store/src/startup.js b/packages/api-plugin-fulfillment-method-pickup-store/src/startup.js new file mode 100644 index 00000000000..1f93a05501c --- /dev/null +++ b/packages/api-plugin-fulfillment-method-pickup-store/src/startup.js @@ -0,0 +1,22 @@ +import ReactionError from "@reactioncommerce/reaction-error"; +import checkAndCreateFulfillmentMethod from "./util/checkAndCreateFulfillmentMethod"; +/** + * @summary Called on startup to create the root entry of this fulfillment type in Fulfillment collection + * @param {Object} context Startup context + * @param {Object} context.collections Map of MongoDB collections + * @returns {undefined} + */ +export default async function fulfillmentMethodPickupStoreStartup(context) { + context.appEvents.on("afterShopCreate", async (payload) => { + const { shop } = payload; + const shopId = shop._id; + + // We do not have validatePermissions in context during this startup stage, hence commenting below + // await context.validatePermissions("reaction:legacy:fulfillmentTypes", "read", { shopId }); + + const insertedMethod = await checkAndCreateFulfillmentMethod(context, shopId); + if (!insertedMethod) { + throw new ReactionError("server-error", "Error in creating fulfillment method"); + } + }); +} diff --git a/packages/api-plugin-fulfillment-method-pickup-store/src/util/__snapshots__/checkAndCreateFulfillmentMethod.test.js.snap b/packages/api-plugin-fulfillment-method-pickup-store/src/util/__snapshots__/checkAndCreateFulfillmentMethod.test.js.snap new file mode 100644 index 00000000000..e08c70088fd --- /dev/null +++ b/packages/api-plugin-fulfillment-method-pickup-store/src/util/__snapshots__/checkAndCreateFulfillmentMethod.test.js.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should throw error and NOT call createFulfillmentMethod mutation 1`] = `"Unable to create fulfillment method Pickup-Store without defined type"`; diff --git a/packages/api-plugin-fulfillment-method-pickup-store/src/util/checkAndCreateFulfillmentMethod.js b/packages/api-plugin-fulfillment-method-pickup-store/src/util/checkAndCreateFulfillmentMethod.js new file mode 100644 index 00000000000..df5df2b78cd --- /dev/null +++ b/packages/api-plugin-fulfillment-method-pickup-store/src/util/checkAndCreateFulfillmentMethod.js @@ -0,0 +1,31 @@ +import ReactionError from "@reactioncommerce/reaction-error"; +/** + * @summary Called on startup to create the root entry of this fulfillment type in Fulfillment collection + * @param {Object} context Startup context + * @param {String} shopId Shop ID + * @returns {Boolean} true if entry exist or insert success else false + */ +export default async function checkAndCreateFulfillmentMethod(context, shopId) { + const { collections } = context; + const { Fulfillment } = collections; + + const pickupRecord = await Fulfillment.findOne({ fulfillmentType: "pickup", shopId }); + if (!pickupRecord) throw new ReactionError("server-error", "Unable to create fulfillment method Pickup-Store without defined type"); + + const fulfillmentTypeId = pickupRecord._id; + const method = { + name: "store", + label: "Pickup from Store", + fulfillmentTypes: ["pickup"], + group: "Ground", + cost: 0, + handling: 0, + rate: 0, + enabled: true, + fulfillmentMethod: "store", + displayMessageMethod: "Placeholder for display message" + }; + + await context.mutations.createFulfillmentMethod(context.getInternalContext(), { shopId, fulfillmentTypeId, method }); + return { method }; +} diff --git a/packages/api-plugin-fulfillment-method-pickup-store/src/util/checkAndCreateFulfillmentMethod.test.js b/packages/api-plugin-fulfillment-method-pickup-store/src/util/checkAndCreateFulfillmentMethod.test.js new file mode 100644 index 00000000000..9bb9869eff8 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-pickup-store/src/util/checkAndCreateFulfillmentMethod.test.js @@ -0,0 +1,89 @@ +import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; +import checkAndCreateFulfillmentMethod from "./checkAndCreateFulfillmentMethod.js"; + +test("should call createFulfillmentMethod mutation", async () => { + const shopId = "SHOP_ID"; + const fulfillment = { + _id: "fulfillment123" + }; + const fulfillmentTypeId = fulfillment._id; + const groupInfo = { + name: "Pickup Provider", + shopId: "SHOP_ID", + provider: { + enabled: true, + label: "Pickup", + name: "pickup" + }, + fulfillmentType: "pickup" + }; + const method = { + name: "store", + label: "Pickup from Store", + fulfillmentTypes: ["pickup"], + group: "Ground", + cost: 0, + handling: 0, + rate: 0, + enabled: true, + fulfillmentMethod: "store", + displayMessageMethod: "Placeholder for display message" + }; + + mockContext.getInternalContext = () => ({ + ...mockContext, + account: null, + accountId: null, + isInternalCall: true, + user: null, + userHasPermission: async () => true, + userId: null, + validatePermissions: async () => undefined + }); + + mockContext.collections.Fulfillment = { + findOne: jest.fn().mockReturnValueOnce(Promise.resolve(groupInfo)) + }; + mockContext.mutations.createFulfillmentMethod = jest.fn().mockName("createFulfillmentMethod").mockReturnValueOnce(Promise.resolve(method)); + + await checkAndCreateFulfillmentMethod(mockContext, { shopId, fulfillmentTypeId, method }); + expect(mockContext.mutations.createFulfillmentMethod).toHaveBeenCalled(); +}); + +test("should throw error and NOT call createFulfillmentMethod mutation", async () => { + const fulfillment = { _id: "fulfillment123" }; + const shopId = "SHOP_ID"; + const fulfillmentTypeId = fulfillment._id; + const method = { + name: "store", + label: "Pickup from Store", + fulfillmentTypes: ["pickup"], + group: "Ground", + cost: 0, + handling: 0, + rate: 0, + enabled: true, + fulfillmentMethod: "store", + displayMessageMethod: "Placeholder for display message" + }; + + mockContext.getInternalContext = () => ({ + ...mockContext, + account: null, + accountId: null, + isInternalCall: true, + user: null, + userHasPermission: async () => true, + userId: null, + validatePermissions: async () => undefined + }); + + mockContext.collections.Fulfillment = { + findOne: jest.fn().mockReturnValueOnce(undefined) + }; + mockContext.mutations.createFulfillmentMethod = jest.fn().mockName("createFulfillmentMethod").mockReturnValueOnce(Promise.resolve(method)); + + await expect(checkAndCreateFulfillmentMethod(mockContext, { shopId, fulfillmentTypeId, method })).rejects.toThrowErrorMatchingSnapshot(); + // await checkAndCreateFulfillmentMethod(mockContext, { shopId, fulfillmentTypeId, method }); + // expect(mockContext.mutations.createFulfillmentMethod).toHaveBeenCalled(); +}); diff --git a/packages/api-plugin-fulfillment-method-pickup-store/src/util/collectStoreDetails.js b/packages/api-plugin-fulfillment-method-pickup-store/src/util/collectStoreDetails.js new file mode 100644 index 00000000000..9b28440b4f9 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-pickup-store/src/util/collectStoreDetails.js @@ -0,0 +1,48 @@ +/** + * @summary Sample dummy function returning store details + * @param {Object} customerAddress - Address of the customer to find nearest store + * @param {Object} [items] - List of items to verify the stock availability + * @returns {Object} [storesArray] - list of available stores + */ +function getStoresArray() { + // Custom logic to find out the list of stores that has the mentioned items in stock + // customerAddress could be used in here and used to figure out the nearest store for listing the results + // items details could be used to verify the availability of the particular item in a store + + return { + gqlType: "storeData", + storeData: [ + { + storeId: "Store-1", + storeAddress: "123, 5th Main, Some place", + storeTiming: "7am to 9pm" + }, + { + storeId: "Store-2", + storeAddress: "456, 50th Main, Some other place", + storeTiming: "7am to 9pm" + } + ] + }; +} + +/** + * @summary Sample dummy function to simulate custom logic to retrieve the rates of the selected method + * @param {Object} method - current method for which rates are to be retrieved + * @param {Object} currentOrder - Current order which provide required details to perform rate calculation + * @returns {Object} updatedMethod - with the rate details populated + */ +export default function collectStoreDetails(method, currentOrder) { + // Calculation of the returned rate could be dependent on currentOrder or + const { items, shippingAddress } = currentOrder; + + // Make call to the Fulfillment method API and collect the rates + // Below we are just hardcoding with some dummy values + const updatedMethod = method; + updatedMethod.rate = 5; + updatedMethod.handling = 10; + updatedMethod.carrier = "Store"; + updatedMethod.methodAdditionalData = getStoresArray(shippingAddress, items); + + return updatedMethod; +} diff --git a/packages/api-plugin-fulfillment-method-pickup-store/src/util/validateOrderMethodsstore.js b/packages/api-plugin-fulfillment-method-pickup-store/src/util/validateOrderMethodsstore.js new file mode 100644 index 00000000000..cf6b6c4aa0b --- /dev/null +++ b/packages/api-plugin-fulfillment-method-pickup-store/src/util/validateOrderMethodsstore.js @@ -0,0 +1,22 @@ +/** + * @summary Sample dummy function validating the data requirements for this metod + * @param {Object} context - Context object + * @param {Object} commonOrder - Current order which provide available details to perform validation + * @param {Object[]} validationResults - Validation results collected till now + * @returns {Object[]} validationResults - with the validation details populated + */ +export default function validateOrderMethodsstore(context, commonOrder, validationResults = []) { +// const { items, shippingAddress } = commonOrder; + + const validationResult = { + errorName: "invalid", + errorType: "ReactionError", + errorField: "Store pickup - some field", + fieldValue: "field-value", + errorMessage: "Customer address not available to find nearest store" + }; + + validationResults.push(validationResult); + + return validationResults; +} diff --git a/packages/api-plugin-fulfillment-type-pickup/.gitignore b/packages/api-plugin-fulfillment-type-pickup/.gitignore new file mode 100644 index 00000000000..ad46b30886f --- /dev/null +++ b/packages/api-plugin-fulfillment-type-pickup/.gitignore @@ -0,0 +1,61 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# next.js build output +.next diff --git a/packages/api-plugin-fulfillment-type-pickup/LICENSE b/packages/api-plugin-fulfillment-type-pickup/LICENSE new file mode 100644 index 00000000000..261eeb9e9f8 --- /dev/null +++ b/packages/api-plugin-fulfillment-type-pickup/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/api-plugin-fulfillment-type-pickup/README.md b/packages/api-plugin-fulfillment-type-pickup/README.md new file mode 100644 index 00000000000..a3c2587c38a --- /dev/null +++ b/packages/api-plugin-fulfillment-type-pickup/README.md @@ -0,0 +1,72 @@ +# api-plugin-fulfillment-type-pickup + +[![npm (scoped)](https://img.shields.io/npm/v/@reactioncommerce/api-plugin-fulfillment-type-pickup.svg)](https://www.npmjs.com/package/@reactioncommerce/api-plugin-fulfillment-type-pickup) +[![CircleCI](https://circleci.com/gh/reactioncommerce/api-plugin-fulfillment-type-pickup.svg?style=svg)](https://circleci.com/gh/reactioncommerce/api-plugin-fulfillment-type-pickup) + + +## Summary + +This plugin implements fulfillment type pickup plugin for [Reaction API](https://github.com/reactioncommerce/reaction). A sample Fulfillment method under 'pickup' is implemented via plugin api-plugin-fulfillment-method-pickup-store. + +The `Developer Certificate of Origin` and `License` sections can stay as they are, assuming `Apache 2` license is used (our preferred license). All other sections of this README should be updated to reflect your plugin. + +## Included in this fulfillment-type-pickup plugin + +### `src/` + +The `src` folder is where you'll put all the plugin files. + +### `.gitignore` + +A basic `gitignore` file + +### `babel.config.cjs` + +If your plugin includes linting and tests, this file is required to allow esmodules to run correctly. + +### `jest.config.cjs` + +If your plugin includes tests, this file is required to allow esmodules to run correctly. You'll need to update the `transformIgnorePatterns` and `moduleNameMapper` sections to include any esmodule `npm` packages used in your plugin. + +### `License.md` + +If your plugin uses `Apache 2` licensing, you can leave this file as-is. If another type of licensing is used, you need to update this file, and the README, accordingly. + +### `package.json` + +The provided `package.json` is set up to install all needed packages and config for linting, testing, and semantic-release. You'll need to update the `name`, `description`, and add any new dependencies your plugin files use. + +### `index.js` + +The entrypoint file for your npm package, will most likely just export your plugin registration from the `src` folder. + +## Developer Certificate of Origin +We use the [Developer Certificate of Origin (DCO)](https://developercertificate.org/) in lieu of a Contributor License Agreement for all contributions to Reaction Commerce open source projects. We request that contributors agree to the terms of the DCO and indicate that agreement by signing all commits made to Reaction Commerce projects by adding a line with your name and email address to every Git commit message contributed: +``` +Signed-off-by: Jane Doe +``` + +You can sign your commit automatically with Git by using `git commit -s` if you have your `user.name` and `user.email` set as part of your Git configuration. + +We ask that you use your real name (please no anonymous contributions or pseudonyms). By signing your commit you are certifying that you have the right have the right to submit it under the open source license used by that particular Reaction Commerce project. You must use your real name (no pseudonyms or anonymous contributions are allowed.) + +We use the [Probot DCO GitHub app](https://github.com/apps/dco) to check for DCO signoffs of every commit. + +If you forget to sign your commits, the DCO bot will remind you and give you detailed instructions for how to amend your commits to add a signature. + +## License + + Copyright 2020 Reaction Commerce + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/packages/api-plugin-fulfillment-type-pickup/babel.config.cjs b/packages/api-plugin-fulfillment-type-pickup/babel.config.cjs new file mode 100644 index 00000000000..5fa924c0809 --- /dev/null +++ b/packages/api-plugin-fulfillment-type-pickup/babel.config.cjs @@ -0,0 +1 @@ +module.exports = require("@reactioncommerce/api-utils/lib/configs/babel.config.cjs"); diff --git a/packages/api-plugin-fulfillment-type-pickup/index.js b/packages/api-plugin-fulfillment-type-pickup/index.js new file mode 100644 index 00000000000..d7ea8b28c59 --- /dev/null +++ b/packages/api-plugin-fulfillment-type-pickup/index.js @@ -0,0 +1,3 @@ +import register from "./src/index.js"; + +export default register; diff --git a/packages/api-plugin-fulfillment-type-pickup/jest.config.cjs b/packages/api-plugin-fulfillment-type-pickup/jest.config.cjs new file mode 100644 index 00000000000..2bdefefceb9 --- /dev/null +++ b/packages/api-plugin-fulfillment-type-pickup/jest.config.cjs @@ -0,0 +1 @@ +module.exports = require("@reactioncommerce/api-utils/lib/configs/jest.config.cjs"); diff --git a/packages/api-plugin-fulfillment-type-pickup/package.json b/packages/api-plugin-fulfillment-type-pickup/package.json new file mode 100644 index 00000000000..818316cc1e2 --- /dev/null +++ b/packages/api-plugin-fulfillment-type-pickup/package.json @@ -0,0 +1,42 @@ +{ + "name": "@reactioncommerce/api-plugin-fulfillment-type-pickup", + "description": "Plugin which implements Pickup as one of the Fulfillment type", + "label": "Fulfillment type Pickup Plugin", + "version": "1.0.0", + "main": "index.js", + "type": "module", + "engines": { + "node": ">=14.18.1", + "npm": ">=7" + }, + "homepage": "https://github.com/reactioncommerce/reaction", + "url": "https://github.com/reactioncommerce/reaction", + "email": "hello-open-commerce@mailchimp.com", + "repository": { + "type": "git", + "url": "git+https://github.com/reactioncommerce/reaction.git", + "directory": "packages/api-plugin-fulfillment-type-pickup" + }, + "author": { + "name": "Mailchimp Open Commerce", + "email": "hello-open-commerce@mailchimp.com", + "url": "https://mailchimp.com/developer/open-commerce/" + }, + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/reactioncommerce/reaction/issues" + }, + "sideEffects": false, + "dependencies": { + "@reactioncommerce/api-utils": "^1.16.9", + "@reactioncommerce/random": "~1.0.2" + }, + "devDependencies": {}, + "scripts": { + "lint": "npm run lint:eslint", + "lint:eslint": "eslint .", + "test": "jest", + "test:watch": "jest --watch", + "test:file": "jest --no-cache --watch --coverage=false" + } +} diff --git a/packages/api-plugin-fulfillment-type-pickup/src/checkAndCreateFulfillmentType.js b/packages/api-plugin-fulfillment-type-pickup/src/checkAndCreateFulfillmentType.js new file mode 100644 index 00000000000..a6fdc156f81 --- /dev/null +++ b/packages/api-plugin-fulfillment-type-pickup/src/checkAndCreateFulfillmentType.js @@ -0,0 +1,26 @@ +/** + * @summary Called on startup to create the root entry of this fulfillment type in Fulfillment collection + * @param {Object} context Startup context + * @param {String} shopId Shop ID + * @returns {Boolean} true if entry exist or insert success else false + */ +export default async function checkAndCreateFulfillmentType(context, shopId) { + const { collections } = context; + const { Fulfillment } = collections; + + const pickupRecord = await Fulfillment.findOne({ fulfillmentType: "pickup", shopId }); + if (!pickupRecord) { + const groupInfo = { + name: "Pickup Provider", + shopId, + provider: { + enabled: true, + label: "Pickup", + name: "pickup" + }, + fulfillmentType: "pickup" + }; + await context.mutations.createFulfillmentType(context.getInternalContext(), groupInfo); + } + return true; +} diff --git a/packages/api-plugin-fulfillment-type-pickup/src/checkAndCreateFulfillmentType.test.js b/packages/api-plugin-fulfillment-type-pickup/src/checkAndCreateFulfillmentType.test.js new file mode 100644 index 00000000000..b64314ff0ef --- /dev/null +++ b/packages/api-plugin-fulfillment-type-pickup/src/checkAndCreateFulfillmentType.test.js @@ -0,0 +1,61 @@ +import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; +import checkAndCreateFulfillmentType from "./checkAndCreateFulfillmentType.js"; + +test("should NOT call createFulfillmentType mutation", async () => { + const shopId = "SHOP_ID"; + const fulfillment = { + _id: "fulfillment123" + }; + const groupInfo = { + name: "Pickup Provider", + shopId: "SHOP_ID", + provider: { + enabled: true, + label: "Pickup", + name: "pickup" + }, + fulfillmentType: "pickup" + }; + mockContext.getInternalContext = () => ({ + ...mockContext, + account: null, + accountId: null, + isInternalCall: true, + user: null, + userHasPermission: async () => true, + userId: null, + validatePermissions: async () => undefined + }); + + mockContext.collections.Fulfillment = { + findOne: jest.fn().mockReturnValueOnce(Promise.resolve(groupInfo)) + }; + mockContext.mutations.createFulfillmentType = jest.fn().mockName("createFulfillmentType").mockReturnValueOnce(Promise.resolve(fulfillment)); + + await checkAndCreateFulfillmentType(mockContext, shopId); + expect(mockContext.mutations.createFulfillmentType).not.toHaveBeenCalled(); +}); + +test("should call createFulfillmentType mutation", async () => { + const fulfillment = { _id: "fulfillment123" }; + const shopId = "SHOP_ID"; + + mockContext.getInternalContext = () => ({ + ...mockContext, + account: null, + accountId: null, + isInternalCall: true, + user: null, + userHasPermission: async () => true, + userId: null, + validatePermissions: async () => undefined + }); + + mockContext.collections.Fulfillment = { + findOne: jest.fn().mockReturnValueOnce(undefined) + }; + mockContext.mutations.createFulfillmentType = jest.fn().mockName("createFulfillmentType").mockReturnValueOnce(Promise.resolve(fulfillment)); + + await checkAndCreateFulfillmentType(mockContext, shopId); + expect(mockContext.mutations.createFulfillmentType).toHaveBeenCalled(); +}); diff --git a/packages/api-plugin-fulfillment-type-pickup/src/index.js b/packages/api-plugin-fulfillment-type-pickup/src/index.js new file mode 100644 index 00000000000..40f27edacdf --- /dev/null +++ b/packages/api-plugin-fulfillment-type-pickup/src/index.js @@ -0,0 +1,26 @@ +import { createRequire } from "module"; +import schemas from "./schemas/index.js"; +import startup from "./startup.js"; + +const require = createRequire(import.meta.url); +const pkg = require("../package.json"); + +/** + * @summary Import and call this function to add this plugin to your API. + * @param {Object} app The ReactionAPI instance + * @returns {undefined} + */ +export default async function register(app) { + await app.registerPlugin({ + label: "Fulfillment Type Pickup", + name: "fulfillment-type-pickup", + version: pkg.version, + graphQL: { + schemas + }, + functionsByType: { + startup: [startup] + }, + registeredFulfillmentTypes: ["pickup"] + }); +} diff --git a/packages/api-plugin-fulfillment-type-pickup/src/schemas/index.js b/packages/api-plugin-fulfillment-type-pickup/src/schemas/index.js new file mode 100644 index 00000000000..30096f92e54 --- /dev/null +++ b/packages/api-plugin-fulfillment-type-pickup/src/schemas/index.js @@ -0,0 +1,5 @@ +import importAsString from "@reactioncommerce/api-utils/importAsString.js"; + +const schema = importAsString("./schema.graphql"); + +export default [schema]; diff --git a/packages/api-plugin-fulfillment-type-pickup/src/schemas/schema.graphql b/packages/api-plugin-fulfillment-type-pickup/src/schemas/schema.graphql new file mode 100644 index 00000000000..e9386d14d4c --- /dev/null +++ b/packages/api-plugin-fulfillment-type-pickup/src/schemas/schema.graphql @@ -0,0 +1,5 @@ +"Allowed fulfillment types, extended by fulfillment-type plugins" +enum FulfillmentType { + "An order will be fulfilled by the customer picking it up" + pickup +} diff --git a/packages/api-plugin-fulfillment-type-pickup/src/startup.js b/packages/api-plugin-fulfillment-type-pickup/src/startup.js new file mode 100644 index 00000000000..401c8f4d7ad --- /dev/null +++ b/packages/api-plugin-fulfillment-type-pickup/src/startup.js @@ -0,0 +1,22 @@ +import ReactionError from "@reactioncommerce/reaction-error"; +import checkAndCreateFulfillmentType from "./checkAndCreateFulfillmentType.js"; +/** + * @summary Called on startup to create the root entry of this fulfillment type in Fulfillment collection + * @param {Object} context Startup context + * @param {Object} context.collections Map of MongoDB collections + * @returns {undefined} + */ +export default async function fulfillmentTypePickupStartup(context) { + context.appEvents.on("afterShopCreate", async (payload) => { + const { shop } = payload; + const shopId = shop._id; + + // We do not have validatePermissions in context during this startup stage, hence commenting below + // await context.validatePermissions("reaction:legacy:fulfillmentTypes", "read", { shopId }); + + const insertSuccess = await checkAndCreateFulfillmentType(context, shopId); + if (!insertSuccess) { + throw new ReactionError("server-error", "Error in creating fulfillment type"); + } + }); +} From 5dbcb91cb20b5ccfa320c03122c3b8091bac2036 Mon Sep 17 00:00:00 2001 From: Sujith Date: Fri, 28 Oct 2022 00:37:54 +0530 Subject: [PATCH 13/71] fix: review comments fixes Signed-off-by: Sujith --- .../.gitignore | 61 ------------------- .../README.md | 46 +++----------- ...FulfillmentMethodsWithQuotesPickupStore.js | 6 +- .../src/index.js | 7 +-- .../src/startup.js | 1 - .../util/checkAndCreateFulfillmentMethod.js | 3 +- .../src/util/validateOrderMethodsstore.js | 19 +++--- .../.gitignore | 61 ------------------- .../README.md | 42 ++----------- .../src/checkAndCreateFulfillmentType.js | 3 +- 10 files changed, 31 insertions(+), 218 deletions(-) delete mode 100644 packages/api-plugin-fulfillment-method-pickup-store/.gitignore delete mode 100644 packages/api-plugin-fulfillment-type-pickup/.gitignore diff --git a/packages/api-plugin-fulfillment-method-pickup-store/.gitignore b/packages/api-plugin-fulfillment-method-pickup-store/.gitignore deleted file mode 100644 index ad46b30886f..00000000000 --- a/packages/api-plugin-fulfillment-method-pickup-store/.gitignore +++ /dev/null @@ -1,61 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# TypeScript v1 declaration files -typings/ - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file -.env - -# next.js build output -.next diff --git a/packages/api-plugin-fulfillment-method-pickup-store/README.md b/packages/api-plugin-fulfillment-method-pickup-store/README.md index 447b4df70a0..3fa11ee62fa 100644 --- a/packages/api-plugin-fulfillment-method-pickup-store/README.md +++ b/packages/api-plugin-fulfillment-method-pickup-store/README.md @@ -1,42 +1,14 @@ # api-plugin-fulfillment-method-pickup-store -[![npm (scoped)](https://img.shields.io/npm/v/@reactioncommerce/api-plugin-fulfillment-method-pickup-store.svg)](https://www.npmjs.com/package/@reactioncommerce/api-plugin-fulfillment-method-pickup-store) -[![CircleCI](https://circleci.com/gh/reactioncommerce/api-plugin-fulfillment-method-pickup-store.svg?style=svg)](https://circleci.com/gh/reactioncommerce/api-plugin-fulfillment-method-pickup-store) - - -## Summary - -This plugin Implements Store pickup as a fulfillment method under the type pickup. - -## Included in this fulfillment-method-pickup-store plugin -ß -### `src/` - -The `src` folder contains all the plugin files. - -### `.gitignore` - -A basic `gitignore` file - -### `babel.config.cjs` - -If your plugin includes linting and tests, this file is required to allow esmodules to run correctly. - -### `jest.config.cjs` - -If your plugin includes tests, this file is required to allow esmodules to run correctly. You'll need to update the `transformIgnorePatterns` and `moduleNameMapper` sections to include any esmodule `npm` packages used in your plugin. - -### `License.md` - -If your plugin uses `Apache 2` licensing, you can leave this file as-is. If another type of licensing is used, you need to update this file, and the README, accordingly. - -### `package.json` - -The provided `package.json` is set up to install all needed packages and config for linting, testing, and semantic-release. You'll need to update the `name`, `description`, and add any new dependencies your plugin files use. - -### `index.js` - -The entrypoint file for your npm package, will most likely just export your plugin registration from the `src` folder. +This is a fulfillment-method plugin which which works along with the base `api-plugin-fulfillment` and the ff-type plugin `api-plugin-fulfillment-type-pickup`. This plugin actually implements all the functionality associated with this fulfillment-method. It can work along-side other fulfillment-methods under the same ff-type. + +This main features/functionalities of this plugin includes the following: +* getFulfillmentMethodsWithQuotesPickupStore - returns the quote or equivalent details for the method when called from base ff plugin +* preStartup - extends the union of "methodAdditionalData" with data structure specific to Store +* startup - Inserts the required ff-method entry into Fulfillment collection +* util/checkAndCreateFulfillmentMethod - confirms existing ff-type entry and adds a new ff-method under it. +* util/collectStoreDetails - dummy function to simulate api providing store specific info while returning quotes. +* util/validateOrderMethodsPickupStore - dummy function to simulate store specific validations. Called by prepareOrder.js ## Developer Certificate of Origin We use the [Developer Certificate of Origin (DCO)](https://developercertificate.org/) in lieu of a Contributor License Agreement for all contributions to Reaction Commerce open source projects. We request that contributors agree to the terms of the DCO and indicate that agreement by signing all commits made to Reaction Commerce projects by adding a line with your name and email address to every Git commit message contributed: diff --git a/packages/api-plugin-fulfillment-method-pickup-store/src/getFulfillmentMethodsWithQuotesPickupStore.js b/packages/api-plugin-fulfillment-method-pickup-store/src/getFulfillmentMethodsWithQuotesPickupStore.js index e3be3ee3658..a43f8693704 100644 --- a/packages/api-plugin-fulfillment-method-pickup-store/src/getFulfillmentMethodsWithQuotesPickupStore.js +++ b/packages/api-plugin-fulfillment-method-pickup-store/src/getFulfillmentMethodsWithQuotesPickupStore.js @@ -19,8 +19,7 @@ const logCtx = { name: "fulfillment-method-pickup-store", file: "getFulfillmentM * @private */ export default async function getFulfillmentMethodsWithQuotesPickupStore(context, commonOrder, previousQueryResults = []) { - const { collections } = context; - const { Fulfillment } = collections; + const { collections: { Fulfillment } } = context; const [rates = [], retrialTargets = []] = previousQueryResults; const currentMethodInfo = { packageName }; @@ -42,7 +41,7 @@ export default async function getFulfillmentMethodsWithQuotesPickupStore(context const initialNumOfRates = rates.length; - const awaitedPickupDocs = pickupDocs.map(async (doc) => { + pickupDocs.map(async (doc) => { const carrier = doc.provider.label; const currentPluginMethods = doc.methods.filter((method) => ((method.name === fulfillmentMethodName) && (method.enabled))); @@ -59,7 +58,6 @@ export default async function getFulfillmentMethodsWithQuotesPickupStore(context }); } }); - await Promise.all(awaitedPickupDocs); if (rates.length === initialNumOfRates) { const errorDetails = { diff --git a/packages/api-plugin-fulfillment-method-pickup-store/src/index.js b/packages/api-plugin-fulfillment-method-pickup-store/src/index.js index d43775c514b..f26c434e73f 100644 --- a/packages/api-plugin-fulfillment-method-pickup-store/src/index.js +++ b/packages/api-plugin-fulfillment-method-pickup-store/src/index.js @@ -4,7 +4,7 @@ import fulfillmentMethodPickupStoreStartup from "./startup.js"; import { MethodStoreData } from "./simpleSchemas.js"; import schemas from "./schemas/index.js"; import getFulfillmentMethodsWithQuotesPickupStore from "./getFulfillmentMethodsWithQuotesPickupStore.js"; -import validateOrderMethodsstore from "./util/validateOrderMethodsstore.js"; +import validateOrderMethodsStore from "./util/validateOrderMethodsStore.js"; const require = createRequire(import.meta.url); const pkg = require("../package.json"); @@ -28,9 +28,8 @@ export default async function register(app) { functionsByType: { preStartup: [fulfillmentMethodPickupStorePreStartup], startup: [fulfillmentMethodPickupStoreStartup], - validateOrderMethods: [validateOrderMethodsstore], - getFulfillmentMethodsWithQuotes: [getFulfillmentMethodsWithQuotesPickupStore], - getFulfillmentMethodsWithQuotesPickup: [getFulfillmentMethodsWithQuotesPickupStore] + validateOrderMethods: [{ key: "store", handler: validateOrderMethodsStore }], + getFulfillmentMethodsWithQuotes: [{ key: "pickup", handler: getFulfillmentMethodsWithQuotesPickupStore }] } }); } diff --git a/packages/api-plugin-fulfillment-method-pickup-store/src/startup.js b/packages/api-plugin-fulfillment-method-pickup-store/src/startup.js index 1f93a05501c..6403f481e6f 100644 --- a/packages/api-plugin-fulfillment-method-pickup-store/src/startup.js +++ b/packages/api-plugin-fulfillment-method-pickup-store/src/startup.js @@ -3,7 +3,6 @@ import checkAndCreateFulfillmentMethod from "./util/checkAndCreateFulfillmentMet /** * @summary Called on startup to create the root entry of this fulfillment type in Fulfillment collection * @param {Object} context Startup context - * @param {Object} context.collections Map of MongoDB collections * @returns {undefined} */ export default async function fulfillmentMethodPickupStoreStartup(context) { diff --git a/packages/api-plugin-fulfillment-method-pickup-store/src/util/checkAndCreateFulfillmentMethod.js b/packages/api-plugin-fulfillment-method-pickup-store/src/util/checkAndCreateFulfillmentMethod.js index df5df2b78cd..23b956a4b65 100644 --- a/packages/api-plugin-fulfillment-method-pickup-store/src/util/checkAndCreateFulfillmentMethod.js +++ b/packages/api-plugin-fulfillment-method-pickup-store/src/util/checkAndCreateFulfillmentMethod.js @@ -6,8 +6,7 @@ import ReactionError from "@reactioncommerce/reaction-error"; * @returns {Boolean} true if entry exist or insert success else false */ export default async function checkAndCreateFulfillmentMethod(context, shopId) { - const { collections } = context; - const { Fulfillment } = collections; + const { collections: { Fulfillment } } = context; const pickupRecord = await Fulfillment.findOne({ fulfillmentType: "pickup", shopId }); if (!pickupRecord) throw new ReactionError("server-error", "Unable to create fulfillment method Pickup-Store without defined type"); diff --git a/packages/api-plugin-fulfillment-method-pickup-store/src/util/validateOrderMethodsstore.js b/packages/api-plugin-fulfillment-method-pickup-store/src/util/validateOrderMethodsstore.js index cf6b6c4aa0b..91e985d2716 100644 --- a/packages/api-plugin-fulfillment-method-pickup-store/src/util/validateOrderMethodsstore.js +++ b/packages/api-plugin-fulfillment-method-pickup-store/src/util/validateOrderMethodsstore.js @@ -6,17 +6,18 @@ * @returns {Object[]} validationResults - with the validation details populated */ export default function validateOrderMethodsstore(context, commonOrder, validationResults = []) { -// const { items, shippingAddress } = commonOrder; +// This is a dummy code to demo how validation results could be returned. +// Commenting out since the placeOrder will fail if the error record is pushed. - const validationResult = { - errorName: "invalid", - errorType: "ReactionError", - errorField: "Store pickup - some field", - fieldValue: "field-value", - errorMessage: "Customer address not available to find nearest store" - }; + // const validationResult = { + // errorName: "invalid", + // errorType: "ReactionError", + // errorField: "Store pickup - some field", + // fieldValue: "field-value", + // errorMessage: "Customer address not available to find nearest store" + // }; - validationResults.push(validationResult); + // validationResults.push(validationResult); return validationResults; } diff --git a/packages/api-plugin-fulfillment-type-pickup/.gitignore b/packages/api-plugin-fulfillment-type-pickup/.gitignore deleted file mode 100644 index ad46b30886f..00000000000 --- a/packages/api-plugin-fulfillment-type-pickup/.gitignore +++ /dev/null @@ -1,61 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# TypeScript v1 declaration files -typings/ - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file -.env - -# next.js build output -.next diff --git a/packages/api-plugin-fulfillment-type-pickup/README.md b/packages/api-plugin-fulfillment-type-pickup/README.md index a3c2587c38a..0e6590ad696 100644 --- a/packages/api-plugin-fulfillment-type-pickup/README.md +++ b/packages/api-plugin-fulfillment-type-pickup/README.md @@ -1,44 +1,12 @@ # api-plugin-fulfillment-type-pickup -[![npm (scoped)](https://img.shields.io/npm/v/@reactioncommerce/api-plugin-fulfillment-type-pickup.svg)](https://www.npmjs.com/package/@reactioncommerce/api-plugin-fulfillment-type-pickup) -[![CircleCI](https://circleci.com/gh/reactioncommerce/api-plugin-fulfillment-type-pickup.svg?style=svg)](https://circleci.com/gh/reactioncommerce/api-plugin-fulfillment-type-pickup) +This is a fulfillment-type plugin which which works along with the base `api-plugin-fulfillment` and other fulfillment-method plugins (like pickup-store). This plugin provides the basic updates needed to establish the fulfillment-type. +Each of the newly introduced fulfillment-method plugins under this fulfillment-type (pickup) would need to bee implemented as separate plugins. -## Summary - -This plugin implements fulfillment type pickup plugin for [Reaction API](https://github.com/reactioncommerce/reaction). A sample Fulfillment method under 'pickup' is implemented via plugin api-plugin-fulfillment-method-pickup-store. - -The `Developer Certificate of Origin` and `License` sections can stay as they are, assuming `Apache 2` license is used (our preferred license). All other sections of this README should be updated to reflect your plugin. - -## Included in this fulfillment-type-pickup plugin - -### `src/` - -The `src` folder is where you'll put all the plugin files. - -### `.gitignore` - -A basic `gitignore` file - -### `babel.config.cjs` - -If your plugin includes linting and tests, this file is required to allow esmodules to run correctly. - -### `jest.config.cjs` - -If your plugin includes tests, this file is required to allow esmodules to run correctly. You'll need to update the `transformIgnorePatterns` and `moduleNameMapper` sections to include any esmodule `npm` packages used in your plugin. - -### `License.md` - -If your plugin uses `Apache 2` licensing, you can leave this file as-is. If another type of licensing is used, you need to update this file, and the README, accordingly. - -### `package.json` - -The provided `package.json` is set up to install all needed packages and config for linting, testing, and semantic-release. You'll need to update the `name`, `description`, and add any new dependencies your plugin files use. - -### `index.js` - -The entrypoint file for your npm package, will most likely just export your plugin registration from the `src` folder. +This plugins does the following: +* Registers the 'pickup' as a ff-type via registeredFulfillmentTypes: ["pickup"] +* Inserts the default entry for pickup ff-type in Fulfillment collection ## Developer Certificate of Origin We use the [Developer Certificate of Origin (DCO)](https://developercertificate.org/) in lieu of a Contributor License Agreement for all contributions to Reaction Commerce open source projects. We request that contributors agree to the terms of the DCO and indicate that agreement by signing all commits made to Reaction Commerce projects by adding a line with your name and email address to every Git commit message contributed: diff --git a/packages/api-plugin-fulfillment-type-pickup/src/checkAndCreateFulfillmentType.js b/packages/api-plugin-fulfillment-type-pickup/src/checkAndCreateFulfillmentType.js index a6fdc156f81..945cc4f98e7 100644 --- a/packages/api-plugin-fulfillment-type-pickup/src/checkAndCreateFulfillmentType.js +++ b/packages/api-plugin-fulfillment-type-pickup/src/checkAndCreateFulfillmentType.js @@ -5,8 +5,7 @@ * @returns {Boolean} true if entry exist or insert success else false */ export default async function checkAndCreateFulfillmentType(context, shopId) { - const { collections } = context; - const { Fulfillment } = collections; + const { collections: { Fulfillment } } = context; const pickupRecord = await Fulfillment.findOne({ fulfillmentType: "pickup", shopId }); if (!pickupRecord) { From ef301ba56844d35053f0bb78a0d1a487c1bd5861 Mon Sep 17 00:00:00 2001 From: Sujith Date: Sat, 29 Oct 2022 11:33:26 +0530 Subject: [PATCH 14/71] fix: import fix Signed-off-by: Sujith --- .../api-plugin-fulfillment-method-pickup-store/src/startup.js | 2 +- packages/api-plugin-fulfillment-type-pickup/package.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/api-plugin-fulfillment-method-pickup-store/src/startup.js b/packages/api-plugin-fulfillment-method-pickup-store/src/startup.js index 6403f481e6f..10547ae3ee0 100644 --- a/packages/api-plugin-fulfillment-method-pickup-store/src/startup.js +++ b/packages/api-plugin-fulfillment-method-pickup-store/src/startup.js @@ -1,5 +1,5 @@ import ReactionError from "@reactioncommerce/reaction-error"; -import checkAndCreateFulfillmentMethod from "./util/checkAndCreateFulfillmentMethod"; +import checkAndCreateFulfillmentMethod from "./util/checkAndCreateFulfillmentMethod.js"; /** * @summary Called on startup to create the root entry of this fulfillment type in Fulfillment collection * @param {Object} context Startup context diff --git a/packages/api-plugin-fulfillment-type-pickup/package.json b/packages/api-plugin-fulfillment-type-pickup/package.json index 818316cc1e2..0695628b175 100644 --- a/packages/api-plugin-fulfillment-type-pickup/package.json +++ b/packages/api-plugin-fulfillment-type-pickup/package.json @@ -29,7 +29,8 @@ "sideEffects": false, "dependencies": { "@reactioncommerce/api-utils": "^1.16.9", - "@reactioncommerce/random": "~1.0.2" + "@reactioncommerce/random": "~1.0.2", + "@reactioncommerce/reaction-error": "1.0.1" }, "devDependencies": {}, "scripts": { From d202e97f6a8ac09c4f8ae8da93b5a89928bcb677 Mon Sep 17 00:00:00 2001 From: Sujith Date: Thu, 3 Nov 2022 15:15:03 +0530 Subject: [PATCH 15/71] fix: pnpm-lock update without snyk Signed-off-by: Sujith --- pnpm-lock.yaml | 26 ++++---------------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a54914d1c58..b0d87ef02a8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -694,45 +694,27 @@ importers: '@reactioncommerce/api-plugin-carts': link:../api-plugin-carts '@reactioncommerce/data-factory': 1.0.1 - packages/api-plugin-fulfillment-method-shipping-flat-rate: + packages/api-plugin-fulfillment-method-pickup-store: specifiers: '@reactioncommerce/api-utils': ^1.16.9 - '@reactioncommerce/logger': ^1.1.3 - '@reactioncommerce/random': ~1.0.2 + '@reactioncommerce/logger': ^1.1.4 '@reactioncommerce/reaction-error': ^1.0.1 simpl-schema: ^1.12.2 dependencies: '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/logger': link:../logger - '@reactioncommerce/random': link:../random - '@reactioncommerce/reaction-error': link:../reaction-error - simpl-schema: 1.12.3 - - packages/api-plugin-fulfillment-method-shipping-ups: - specifiers: - '@reactioncommerce/api-utils': ^1.16.9 - '@reactioncommerce/reaction-error': ^1.0.1 - simpl-schema: ^1.12.2 - dependencies: - '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/reaction-error': link:../reaction-error simpl-schema: 1.12.3 - packages/api-plugin-fulfillment-type-shipping: + packages/api-plugin-fulfillment-type-pickup: specifiers: '@reactioncommerce/api-utils': ^1.16.9 - '@reactioncommerce/logger': ^1.1.3 '@reactioncommerce/random': ~1.0.2 - '@reactioncommerce/reaction-error': ^1.0.1 - lodash: ^4.17.21 - simpl-schema: ^1.12.2 + '@reactioncommerce/reaction-error': 1.0.1 dependencies: '@reactioncommerce/api-utils': link:../api-utils - '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error - lodash: 4.17.21 - simpl-schema: 1.12.3 packages/api-plugin-i18n: specifiers: From e0c056ea1415f0e3eab6d1fc8471c730557d22bd Mon Sep 17 00:00:00 2001 From: Sujith Date: Tue, 18 Oct 2022 15:42:07 +0530 Subject: [PATCH 16/71] feat: fftype-shipping with methods flatrate ups Signed-off-by: Sujith --- .../.gitignore | 61 ++ .../LICENSE | 201 +++++ .../README.md | 69 ++ .../babel.config.cjs | 1 + .../index.js | 3 + .../jest.config.cjs | 1 + .../package.json | 45 + ...llmentMethodsWithQuotesShippingFlatRate.js | 114 +++ .../src/index.js | 58 ++ .../createFlatRateFulfillmentMethod.js | 53 ++ .../createFlatRateFulfillmentRestriction.js | 35 + ...eateFlatRateFulfillmentRestriction.test.js | 39 + .../deleteFlatRateFulfillmentMethod.js | 45 + .../deleteFlatRateFulfillmentRestriction.js | 32 + ...leteFlatRateFulfillmentRestriction.test.js | 40 + .../src/mutations/index.js | 15 + .../updateFlatRateFulfillmentMethod.js | 54 ++ .../updateFlatRateFulfillmentRestriction.js | 46 + ...dateFlatRateFulfillmentRestriction.test.js | 52 ++ .../src/policies.json | 55 ++ .../src/preStartup.js | 27 + .../src/queries/flatRateFulfillmentMethod.js | 31 + .../src/queries/flatRateFulfillmentMethods.js | 48 ++ .../getFlatRateFulfillmentRestriction.js | 21 + .../getFlatRateFulfillmentRestrictions.js | 20 + .../src/queries/index.js | 11 + .../FlatRateFulfillmentMethod/index.js | 8 + .../FlatRateFulfillmentRestriction/index.js | 11 + .../createFlatRateFulfillmentMethod.js | 31 + .../createFlatRateFulfillmentRestriction.js | 38 + .../deleteFlatRateFulfillmentMethod.js | 32 + .../deleteFlatRateFulfillmentRestriction.js | 32 + .../src/resolvers/Mutation/index.js | 15 + .../updateFlatRateFulfillmentMethod.js | 34 + .../updateFlatRateFulfillmentRestriction.js | 45 + .../Query/flatRateFulfillmentMethod.js | 28 + .../Query/flatRateFulfillmentMethods.js | 31 + .../getFlatRateFulfillmentRestriction.js | 21 + .../getFlatRateFulfillmentRestrictions.js | 29 + .../src/resolvers/Query/index.js | 11 + .../src/resolvers/ShopSettings/index.js | 9 + .../src/resolvers/index.js | 16 + .../src/schemas/index.js | 6 + .../src/schemas/restrictions.graphql | 297 +++++++ .../src/schemas/schema.graphql | 264 ++++++ .../src/simpleSchemas.js | 17 + .../src/util/attributeDenyCheck.js | 48 ++ .../src/util/filterShippingMethods.js | 48 ++ .../src/util/filterShippingMethods.test.js | 816 ++++++++++++++++++ .../src/util/isDestinationRestricted.js | 42 + .../src/util/isShippingRestricted.js | 53 ++ .../src/util/locationAllowCheck.js | 27 + .../src/util/locationDenyCheck.js | 27 + .../src/util/methodSchema.js | 25 + .../src/util/restrictionSchema.js | 63 ++ .../src/util/validateOrderMethodsflatrate.js | 23 + .../src/xforms/id.js | 16 + .../.gitignore | 61 ++ .../LICENSE | 201 +++++ .../README.md | 70 ++ .../babel.config.cjs | 1 + .../index.js | 3 + .../jest.config.cjs | 1 + .../package.json | 43 + ...FulfillmentMethodsWithQuotesShippingUPS.js | 72 ++ .../src/index.js | 36 + .../src/preStartup.js | 28 + .../src/schemas/index.js | 5 + .../src/schemas/schema.graphql | 7 + .../src/simpleSchemas.js | 17 + .../src/startup.js | 39 + .../src/util/calculateUPSRate.js | 33 + .../src/util/sampleTest.test.js | 3 + .../src/util/validateOrderMethodsups.js | 22 + .../.gitignore | 61 ++ .../LICENSE | 201 +++++ .../README.md | 72 ++ .../babel.config.cjs | 1 + .../index.js | 3 + .../jest.config.cjs | 1 + .../package.json | 46 + .../src/index.js | 33 + .../src/sampleTest.test.js | 3 + .../src/schemas/index.js | 5 + .../src/schemas/schema.graphql | 29 + .../src/startup.js | 37 + 86 files changed, 4474 insertions(+) create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/.gitignore create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/LICENSE create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/README.md create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/babel.config.cjs create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/index.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/jest.config.cjs create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/package.json create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/getFulfillmentMethodsWithQuotesShippingFlatRate.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/index.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/createFlatRateFulfillmentMethod.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/createFlatRateFulfillmentRestriction.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/createFlatRateFulfillmentRestriction.test.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/deleteFlatRateFulfillmentMethod.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/deleteFlatRateFulfillmentRestriction.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/deleteFlatRateFulfillmentRestriction.test.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/index.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentMethod.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentRestriction.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentRestriction.test.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/policies.json create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/preStartup.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/flatRateFulfillmentMethod.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/flatRateFulfillmentMethods.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/getFlatRateFulfillmentRestriction.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/getFlatRateFulfillmentRestrictions.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/index.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/FlatRateFulfillmentMethod/index.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/FlatRateFulfillmentRestriction/index.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Mutation/createFlatRateFulfillmentMethod.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Mutation/createFlatRateFulfillmentRestriction.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Mutation/deleteFlatRateFulfillmentMethod.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Mutation/deleteFlatRateFulfillmentRestriction.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Mutation/index.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Mutation/updateFlatRateFulfillmentMethod.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Mutation/updateFlatRateFulfillmentRestriction.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Query/flatRateFulfillmentMethod.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Query/flatRateFulfillmentMethods.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Query/getFlatRateFulfillmentRestriction.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Query/getFlatRateFulfillmentRestrictions.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Query/index.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/ShopSettings/index.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/index.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/schemas/index.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/schemas/restrictions.graphql create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/schemas/schema.graphql create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/simpleSchemas.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/attributeDenyCheck.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/filterShippingMethods.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/filterShippingMethods.test.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/isDestinationRestricted.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/isShippingRestricted.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/locationAllowCheck.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/locationDenyCheck.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/methodSchema.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/restrictionSchema.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/validateOrderMethodsflatrate.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/xforms/id.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-ups/.gitignore create mode 100644 packages/api-plugin-fulfillment-method-shipping-ups/LICENSE create mode 100644 packages/api-plugin-fulfillment-method-shipping-ups/README.md create mode 100644 packages/api-plugin-fulfillment-method-shipping-ups/babel.config.cjs create mode 100644 packages/api-plugin-fulfillment-method-shipping-ups/index.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-ups/jest.config.cjs create mode 100644 packages/api-plugin-fulfillment-method-shipping-ups/package.json create mode 100644 packages/api-plugin-fulfillment-method-shipping-ups/src/getFulfillmentMethodsWithQuotesShippingUPS.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-ups/src/index.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-ups/src/preStartup.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-ups/src/schemas/index.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-ups/src/schemas/schema.graphql create mode 100644 packages/api-plugin-fulfillment-method-shipping-ups/src/simpleSchemas.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-ups/src/startup.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-ups/src/util/calculateUPSRate.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-ups/src/util/sampleTest.test.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-ups/src/util/validateOrderMethodsups.js create mode 100644 packages/api-plugin-fulfillment-type-shipping/.gitignore create mode 100644 packages/api-plugin-fulfillment-type-shipping/LICENSE create mode 100644 packages/api-plugin-fulfillment-type-shipping/README.md create mode 100644 packages/api-plugin-fulfillment-type-shipping/babel.config.cjs create mode 100644 packages/api-plugin-fulfillment-type-shipping/index.js create mode 100644 packages/api-plugin-fulfillment-type-shipping/jest.config.cjs create mode 100644 packages/api-plugin-fulfillment-type-shipping/package.json create mode 100644 packages/api-plugin-fulfillment-type-shipping/src/index.js create mode 100644 packages/api-plugin-fulfillment-type-shipping/src/sampleTest.test.js create mode 100644 packages/api-plugin-fulfillment-type-shipping/src/schemas/index.js create mode 100644 packages/api-plugin-fulfillment-type-shipping/src/schemas/schema.graphql create mode 100644 packages/api-plugin-fulfillment-type-shipping/src/startup.js diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/.gitignore b/packages/api-plugin-fulfillment-method-shipping-flat-rate/.gitignore new file mode 100644 index 00000000000..ad46b30886f --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/.gitignore @@ -0,0 +1,61 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# next.js build output +.next diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/LICENSE b/packages/api-plugin-fulfillment-method-shipping-flat-rate/LICENSE new file mode 100644 index 00000000000..261eeb9e9f8 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/README.md b/packages/api-plugin-fulfillment-method-shipping-flat-rate/README.md new file mode 100644 index 00000000000..14388ca70df --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/README.md @@ -0,0 +1,69 @@ +# api-plugin-fulfillment-method-shipping-flat-rate + +[![npm (scoped)](https://img.shields.io/npm/v/@reactioncommerce/api-plugin-fulfillment-method-shipping-flat-rate.svg)](https://www.npmjs.com/package/@reactioncommerce/api-plugin-fulfillment-method-shipping-flat-rate) +[![CircleCI](https://circleci.com/gh/reactioncommerce/api-plugin-fulfillment-method-shipping-flat-rate.svg?style=svg)](https://circleci.com/gh/reactioncommerce/api-plugin-fulfillment-method-shipping-flat-rate) + +## Summary + +This plugin provides the functionalities of a sample fulfillment method flat-rate under the fulfillment type of shipping. + +## Included in this fulfillment-method-shipping-flat-rate plugin + +### `src/` + +The `src` folder with all the plugin files. + +### `.gitignore` + +A basic `gitignore` file + +### `babel.config.cjs` + +If your plugin includes linting and tests, this file is required to allow esmodules to run correctly. + +### `jest.config.cjs` + +If your plugin includes tests, this file is required to allow esmodules to run correctly. You'll need to update the `transformIgnorePatterns` and `moduleNameMapper` sections to include any esmodule `npm` packages used in your plugin. + +### `License.md` + +If your plugin uses `Apache 2` licensing, you can leave this file as-is. If another type of licensing is used, you need to update this file, and the README, accordingly. + +### `package.json` + +The provided `package.json` is set up to install all needed packages and config for linting, testing, and semantic-release. You'll need to update the `name`, `description`, and add any new dependencies your plugin files use. + +### `index.js` + +The entrypoint file for your npm package, will most likely just export your plugin registration from the `src` folder. + +## Developer Certificate of Origin +We use the [Developer Certificate of Origin (DCO)](https://developercertificate.org/) in lieu of a Contributor License Agreement for all contributions to Reaction Commerce open source projects. We request that contributors agree to the terms of the DCO and indicate that agreement by signing all commits made to Reaction Commerce projects by adding a line with your name and email address to every Git commit message contributed: +``` +Signed-off-by: Jane Doe +``` + +You can sign your commit automatically with Git by using `git commit -s` if you have your `user.name` and `user.email` set as part of your Git configuration. + +We ask that you use your real name (please no anonymous contributions or pseudonyms). By signing your commit you are certifying that you have the right have the right to submit it under the open source license used by that particular Reaction Commerce project. You must use your real name (no pseudonyms or anonymous contributions are allowed.) + +We use the [Probot DCO GitHub app](https://github.com/apps/dco) to check for DCO signoffs of every commit. + +If you forget to sign your commits, the DCO bot will remind you and give you detailed instructions for how to amend your commits to add a signature. + +## License + + Copyright 2020 Reaction Commerce + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/babel.config.cjs b/packages/api-plugin-fulfillment-method-shipping-flat-rate/babel.config.cjs new file mode 100644 index 00000000000..5fa924c0809 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/babel.config.cjs @@ -0,0 +1 @@ +module.exports = require("@reactioncommerce/api-utils/lib/configs/babel.config.cjs"); diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/index.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/index.js new file mode 100644 index 00000000000..d7ea8b28c59 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/index.js @@ -0,0 +1,3 @@ +import register from "./src/index.js"; + +export default register; diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/jest.config.cjs b/packages/api-plugin-fulfillment-method-shipping-flat-rate/jest.config.cjs new file mode 100644 index 00000000000..2bdefefceb9 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/jest.config.cjs @@ -0,0 +1 @@ +module.exports = require("@reactioncommerce/api-utils/lib/configs/jest.config.cjs"); diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/package.json b/packages/api-plugin-fulfillment-method-shipping-flat-rate/package.json new file mode 100644 index 00000000000..d8dbd468daf --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/package.json @@ -0,0 +1,45 @@ +{ + "name": "@reactioncommerce/api-plugin-fulfillment-method-shipping-flat-rate", + "description": "Plugin for Fulfillment method flat-rate under the type shipping", + "label": "Fulfillment Method Shipping Flat Rate Plugin", + "version": "1.0.0", + "main": "index.js", + "type": "module", + "engines": { + "node": ">=14.18.1", + "npm": ">=7" + }, + "homepage": "https://github.com/reactioncommerce/reaction", + "url": "https://github.com/reactioncommerce/reaction", + "email": "hello-open-commerce@mailchimp.com", + "repository": { + "type": "git", + "url": "git+https://github.com/reactioncommerce/reaction.git", + "directory": "packages/api-plugin-fulfillment-method-shipping-flat-rate" + }, + "author": { + "name": "Mailchimp Open Commerce", + "email": "hello-open-commerce@mailchimp.com", + "url": "https://mailchimp.com/developer/open-commerce/" + }, + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/reactioncommerce/reaction/issues" + }, + "sideEffects": false, + "dependencies": { + "simpl-schema": "^1.12.2", + "@reactioncommerce/logger": "^1.1.3", + "@reactioncommerce/reaction-error": "^1.0.1", + "@reactioncommerce/random": "~1.0.2", + "@reactioncommerce/api-utils": "^1.16.9" + }, + "devDependencies": {}, + "scripts": { + "lint": "npm run lint:eslint", + "lint:eslint": "eslint .", + "test": "jest", + "test:watch": "jest --watch", + "test:file": "jest --no-cache --watch --coverage=false" + } +} diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/getFulfillmentMethodsWithQuotesShippingFlatRate.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/getFulfillmentMethodsWithQuotesShippingFlatRate.js new file mode 100644 index 00000000000..b59da101399 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/getFulfillmentMethodsWithQuotesShippingFlatRate.js @@ -0,0 +1,114 @@ +import Logger from "@reactioncommerce/logger"; +import isShippingRestricted from "./util/isShippingRestricted.js"; +import filterShippingMethods from "./util/filterShippingMethods.js"; +import { logCtx } from "./index.js"; + +const packageName = "fulfillment-method-shipping-flat-rate"; +const fulfillmentTypeName = "shipping"; +const fulfillmentMethodName = "flatRate"; + +/** + * @summary Returns a list of fulfillment method quotes based on the items in a fulfillment group. + * @param {Object} context - Context + * @param {Object} commonOrder - details about the purchase a user wants to make. + * @param {Array} [previousQueryResults] - an array of shipping rates and + * info about failed calls to the APIs of some shipping methods providers + * @returns {Array} - an array that contains two arrays: the first array will + * be an updated list of shipping rates, and the second will contain info for + * retrying this specific package if any errors occurred while retrieving the + * shipping rates. + * @private + */ +export default async function getFulfillmentMethodsWithQuotesShippingFlatRate(context, commonOrder, previousQueryResults = []) { + const { collections } = context; + const { Fulfillment } = collections; + const [rates = [], retrialTargets = []] = previousQueryResults; + const currentMethodInfo = { packageName }; + + logCtx.file = "src/getFulfillmentMethodsWithQuotesShippingFlatRate.js"; + + if (retrialTargets.length > 0) { + const isNotAmongFailedRequests = retrialTargets.every((target) => target.packageName !== packageName); + if (isNotAmongFailedRequests) { + return previousQueryResults; + } + } + + const { isShippingRatesFulfillmentEnabled } = await context.queries.appSettings(context, commonOrder.shopId); + if (!isShippingRatesFulfillmentEnabled) { + return [rates, retrialTargets]; + } + + // Above validation is retained for backward compatibility. Below validation is go-forward way + const shippingRateDocs = await Fulfillment.find({ + "shopId": commonOrder.shopId, + "fulfillmentType": fulfillmentTypeName, + "provider.enabled": true + }).toArray(); + if (!shippingRateDocs || !shippingRateDocs.length) { + return [rates, retrialTargets]; + } + + const initialNumOfRates = rates.length; + + // Get hydrated order, an object of current order data including item and destination information + const isOrderShippingRestricted = await isShippingRestricted(context, commonOrder); + + if (isOrderShippingRestricted) { + const errorDetails = { + requestStatus: "error", + shippingProvider: packageName, + message: "Flat rate shipping did not return any shipping methods." + }; + rates.push(errorDetails); + } else { + const awaitedShippingRateDocs = shippingRateDocs.map(async (doc) => { + const carrier = doc.provider.label; + const currentPluginMethods = doc.methods.filter((method) => ((method.fulfillmentMethod === fulfillmentMethodName) && (method.enabled))); + // Check for method specific shipping restrictions + const availableShippingMethods = await filterShippingMethods(context, currentPluginMethods, commonOrder); + + for (const method of availableShippingMethods) { + if (!method.rate) { + method.rate = 0; + } + if (!method.handling) { + method.handling = 0; + } + // Store shipping provider here in order to have it available in shipmentMethod + // for cart and order usage + if (!method.carrier) { + method.carrier = carrier; + } + method.methodAdditionalData = { // currently hard coded as a dummy entry, replace with a function + gqlType: "flatRateData", + flatRateData: 321 + }; + + rates.push({ + carrier, + handlingPrice: method.handling, + method, + rate: method.rate, + shippingPrice: method.rate + method.handling, + shopId: doc.shopId + }); + } + }); + await Promise.all(awaitedShippingRateDocs); + } + + if (rates.length === initialNumOfRates) { + const errorDetails = { + requestStatus: "error", + shippingProvider: packageName, + message: "Flat rate shipping did not return any shipping methods." + }; + rates.push(errorDetails); + retrialTargets.push(currentMethodInfo); + return [rates, retrialTargets]; + } + + Logger.debug({ ...logCtx, rates }, "Flat rates returned"); + return [rates, retrialTargets]; +} diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/index.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/index.js new file mode 100644 index 00000000000..cf3c7fd12c8 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/index.js @@ -0,0 +1,58 @@ +import { createRequire } from "module"; +import getFulfillmentMethodsWithQuotesShippingFlatRate from "./getFulfillmentMethodsWithQuotesShippingFlatRate.js"; +import validateOrderMethodsflatRate from "./util/validateOrderMethodsflatrate.js"; +import resolvers from "./resolvers/index.js"; +import mutations from "./mutations/index.js"; +import policies from "./policies.json"; +import queries from "./queries/index.js"; +import schemas from "./schemas/index.js"; +import fulfillmentMethodShippingFlatRatePreStartup from "./preStartup.js"; +import { MethodFlatRateData } from "./simpleSchemas.js"; + +const require = createRequire(import.meta.url); +const pkg = require("../package.json"); + +const { name, version } = pkg; +export const logCtx = { + name, + version, + file: "src/index.js" +}; + +/** + * @summary Import and call this function to add this plugin to your API. + * @param {Object} app The ReactionAPI instance + * @returns {undefined} + */ +export default async function register(app) { + await app.registerPlugin({ + label: "Fulfillment Method Shipping Flat Rate", + name: "fulfillment-method-shipping-flat-rate", + version: pkg.version, + graphQL: { + resolvers, + schemas + }, + mutations, + policies, + queries, + simpleSchemas: { + MethodFlatRateData + }, + functionsByType: { + preStartup: [fulfillmentMethodShippingFlatRatePreStartup], + validateOrderMethods: [validateOrderMethodsflatRate], + getFulfillmentMethodsWithQuotes: [getFulfillmentMethodsWithQuotesShippingFlatRate], + getFulfillmentMethodsWithQuotesShipping: [getFulfillmentMethodsWithQuotesShippingFlatRate] + }, + shopSettingsConfig: { + isShippingRatesFulfillmentEnabled: { + defaultValue: true, + permissionsThatCanEdit: ["reaction:legacy:shippingMethods/update:settings"], + simpleSchema: { + type: Boolean + } + } + } + }); +} diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/createFlatRateFulfillmentMethod.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/createFlatRateFulfillmentMethod.js new file mode 100644 index 00000000000..1b3059dbf68 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/createFlatRateFulfillmentMethod.js @@ -0,0 +1,53 @@ +import SimpleSchema from "simpl-schema"; +import Random from "@reactioncommerce/random"; +import ReactionError from "@reactioncommerce/reaction-error"; +import methodSchema from "../util/methodSchema.js"; + +const inputSchema = new SimpleSchema({ + method: methodSchema, + shopId: String +}); + +/** + * @method createFlatRateFulfillmentMethodMutation + * @summary Creates a flat rate fulfillment method + * @param {Object} context - an object containing the per-request state + * @param {Object} input - Input (see SimpleSchema) + * @returns {Promise} An object with a `method` property containing the created method + */ +export default async function createFlatRateFulfillmentMethodMutation(context, input) { + const cleanedInput = inputSchema.clean(input); // add default values and such + inputSchema.validate(cleanedInput); + + const { method: inputMethod, shopId } = cleanedInput; + const { collections } = context; + const { Fulfillment } = collections; + const method = { ...inputMethod }; + + await context.validatePermissions("reaction:legacy:fulfillmentMethods", "create", { shopId }); + + const shippingRecord = await Fulfillment.findOne({ fulfillmentType: "shipping", shopId }); + if (!shippingRecord) throw new ReactionError("server-error", "Unable to create fulfillment method without defined type"); + + method._id = Random.id(); + // MongoDB schema still uses `enabled` rather than `isEnabled` + method.enabled = method.isEnabled; + delete method.isEnabled; + + // Hardcoded field, each ff-method plugin has to introduce this field for grouping purpose + // Schema defined as optional=true for backward compatibility + method.fulfillmentMethod = "flatRate"; + + const { matchedCount } = await Fulfillment.updateOne({ + shopId, + fulfillmentType: "shipping" + }, { + $addToSet: { + methods: method + } + }); + + if (matchedCount === 0) throw new ReactionError("server-error", "Unable to create fulfillment method"); + + return { method }; +} diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/createFlatRateFulfillmentRestriction.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/createFlatRateFulfillmentRestriction.js new file mode 100644 index 00000000000..5fe7e7889ce --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/createFlatRateFulfillmentRestriction.js @@ -0,0 +1,35 @@ +import SimpleSchema from "simpl-schema"; +import Random from "@reactioncommerce/random"; +import ReactionError from "@reactioncommerce/reaction-error"; +import restrictionSchema from "../util/restrictionSchema.js"; + +const inputSchema = new SimpleSchema({ + restriction: restrictionSchema, + shopId: String +}); + +/** + * @method createFlatRateFulfillmentRestrictionMutation + * @summary Creates a flat rate fulfillment restriction + * @param {Object} context - an object containing the per-request state + * @param {Object} input - Input (see SimpleSchema) + * @returns {Promise} An object with a `restriction` property containing the created restriction + */ +export default async function createFlatRateFulfillmentRestrictionMutation(context, input) { + const cleanedInput = inputSchema.clean(input); // add default values and such + inputSchema.validate(cleanedInput); + + const { restriction, shopId } = cleanedInput; + const { collections } = context; + const { FulfillmentRestrictions } = collections; + + await context.validatePermissions("reaction:legacy:fulfillmentRestrictions", "create", { shopId }); + + restriction._id = Random.id(); + restriction.shopId = shopId; + + const { insertedCount } = await FulfillmentRestrictions.insertOne(restriction); + if (insertedCount === 0) throw new ReactionError("server-error", "Unable to create restriction method"); + + return { restriction }; +} diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/createFlatRateFulfillmentRestriction.test.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/createFlatRateFulfillmentRestriction.test.js new file mode 100644 index 00000000000..fab5925a998 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/createFlatRateFulfillmentRestriction.test.js @@ -0,0 +1,39 @@ +import mockCollection from "@reactioncommerce/api-utils/tests/mockCollection.js"; +import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; +import createFlatRateFulfillmentRestrictionMutation from "./createFlatRateFulfillmentRestriction.js"; + +mockContext.validatePermissions = jest.fn().mockName("validatePermissions"); +// Create mock context with FulfillmentRestrictions collection +mockContext.collections.FulfillmentRestrictions = mockCollection("FulfillmentRestrictions"); +mockContext.validatePermissions.mockReturnValueOnce(Promise.resolve(null)); + +test("add a flat rate fulfillment restriction", async () => { + // mockContext.collections.FlatRateFulfillmentRestrictions.insertOne.mockReturnValueOnce(Promise.resolve({})); + mockContext.collections.FulfillmentRestrictions.insertOne.mockReturnValueOnce(Promise.resolve({})); + + const result = await createFlatRateFulfillmentRestrictionMutation(mockContext, { + _id: "restriction123", + shopId: "shop123", + restriction: { + type: "deny", + attributes: [ + { property: "vendor", value: "reaction", propertyType: "string", operator: "eq" }, + { property: "productType", value: "knife", propertyType: "string", operator: "eq" } + ], + destination: { region: ["CO", "NY"] } + } + }); + + expect(result).toEqual({ + restriction: { + _id: expect.any(String), + shopId: "shop123", + type: "deny", + attributes: [ + { property: "vendor", value: "reaction", propertyType: "string", operator: "eq" }, + { property: "productType", value: "knife", propertyType: "string", operator: "eq" } + ], + destination: { region: ["CO", "NY"] } + } + }); +}); diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/deleteFlatRateFulfillmentMethod.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/deleteFlatRateFulfillmentMethod.js new file mode 100644 index 00000000000..c114c722a82 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/deleteFlatRateFulfillmentMethod.js @@ -0,0 +1,45 @@ +import SimpleSchema from "simpl-schema"; +import ReactionError from "@reactioncommerce/reaction-error"; + +const inputSchema = new SimpleSchema({ + methodId: String, + shopId: String +}); + +/** + * @method deleteFlatRateFulfillmentMethodMutation + * @summary deletes a flat rate fulfillment method + * @param {Object} context - an object containing the per-request state + * @param {Object} input - Input (see SimpleSchema) + * @returns {Promise} An object with a `method` property containing the deleted method + */ +export default async function deleteFlatRateFulfillmentMethodMutation(context, input) { + inputSchema.validate(input); + + const { methodId, shopId } = input; + const { collections } = context; + const { Fulfillment } = collections; + + await context.validatePermissions(`reaction:legacy:fulfillmentMethods:${methodId}`, "delete", { shopId }); + + const shippingRecord = await Fulfillment.findOne({ + "methods._id": methodId, + shopId + }); + if (!shippingRecord) throw new ReactionError("not-found", "Shipping method not found"); + + const { matchedCount } = await Fulfillment.updateOne({ + "methods._id": methodId, + shopId + }, { + $pull: { + methods: { + _id: methodId + } + } + }); + if (matchedCount === 0) throw new ReactionError("server-error", "Unable to update the Shipping method"); + + const method = shippingRecord.methods.find((meth) => meth._id === methodId); + return { method }; +} diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/deleteFlatRateFulfillmentRestriction.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/deleteFlatRateFulfillmentRestriction.js new file mode 100644 index 00000000000..f8773de022f --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/deleteFlatRateFulfillmentRestriction.js @@ -0,0 +1,32 @@ +import SimpleSchema from "simpl-schema"; +import ReactionError from "@reactioncommerce/reaction-error"; + +const inputSchema = new SimpleSchema({ + restrictionId: String, + shopId: String +}); + +/** + * @method deleteFlatRateFulfillmentRestriction + * @summary deletes a flat rate fulfillment restriction + * @param {Object} context - an object containing the per-request state + * @param {Object} input - Input (see SimpleSchema) + * @returns {Promise} An object with a `restriction` property containing the deleted restriction + */ +export default async function deleteFlatRateFulfillmentRestriction(context, input) { + inputSchema.validate(input); + + const { restrictionId, shopId } = input; + const { collections } = context; + const { FulfillmentRestrictions } = collections; + + await context.validatePermissions(`reaction:legacy:fulfillmentRestrictions:${restrictionId}`, "delete", { shopId }); + + const { ok, value } = await FulfillmentRestrictions.findOneAndDelete({ + _id: restrictionId, + shopId + }); + if (ok !== 1) throw new ReactionError("not-found", "Not found"); + + return { restriction: value }; +} diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/deleteFlatRateFulfillmentRestriction.test.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/deleteFlatRateFulfillmentRestriction.test.js new file mode 100644 index 00000000000..8f48f37e62e --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/deleteFlatRateFulfillmentRestriction.test.js @@ -0,0 +1,40 @@ +import mockCollection from "@reactioncommerce/api-utils/tests/mockCollection.js"; +import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; +import deleteFlatRateFulfillmentRestrictionMutation from "./deleteFlatRateFulfillmentRestriction.js"; + + +// Create mock context with FulfillmentRestrictions collection +mockContext.collections.FulfillmentRestrictions = mockCollection("FulfillmentRestrictions"); +mockContext.validatePermissions.mockReturnValueOnce(Promise.resolve(null)); + +const value = { + type: "deny", + attributes: [ + { property: "vendor", value: "reaction", propertyType: "string", operator: "eq" }, + { property: "productType", value: "knife", propertyType: "string", operator: "eq" } + ], + destination: { region: ["CO", "NY"] } +}; + +test("delete a flat rate fulfillment restriction", async () => { + mockContext.collections.FulfillmentRestrictions.findOneAndDelete.mockReturnValueOnce(Promise.resolve({ + ok: 1, + value + })); + + const result = await deleteFlatRateFulfillmentRestrictionMutation(mockContext, { + restrictionId: "restriction123", + shopId: "shop123" + }); + + expect(result).toEqual({ + restriction: { + type: "deny", + attributes: [ + { property: "vendor", value: "reaction", propertyType: "string", operator: "eq" }, + { property: "productType", value: "knife", propertyType: "string", operator: "eq" } + ], + destination: { region: ["CO", "NY"] } + } + }); +}); diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/index.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/index.js new file mode 100644 index 00000000000..b07a122922c --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/index.js @@ -0,0 +1,15 @@ +import createFlatRateFulfillmentMethod from "./createFlatRateFulfillmentMethod.js"; +import deleteFlatRateFulfillmentMethod from "./deleteFlatRateFulfillmentMethod.js"; +import updateFlatRateFulfillmentMethod from "./updateFlatRateFulfillmentMethod.js"; +import createFlatRateFulfillmentRestriction from "./createFlatRateFulfillmentRestriction.js"; +import deleteFlatRateFulfillmentRestriction from "./deleteFlatRateFulfillmentRestriction.js"; +import updateFlatRateFulfillmentRestriction from "./updateFlatRateFulfillmentRestriction.js"; + +export default { + createFlatRateFulfillmentMethod, + deleteFlatRateFulfillmentMethod, + updateFlatRateFulfillmentMethod, + createFlatRateFulfillmentRestriction, + deleteFlatRateFulfillmentRestriction, + updateFlatRateFulfillmentRestriction +}; diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentMethod.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentMethod.js new file mode 100644 index 00000000000..aa4bb26f997 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentMethod.js @@ -0,0 +1,54 @@ +import SimpleSchema from "simpl-schema"; +import ReactionError from "@reactioncommerce/reaction-error"; +import methodSchema from "../util/methodSchema.js"; + +const inputSchema = new SimpleSchema({ + method: methodSchema, + methodId: String, + shopId: String +}); + +/** + * @method updateFlatRateFulfillmentMethodMutation + * @summary updates a flat rate fulfillment method + * @param {Object} context - an object containing the per-request state + * @param {Object} input - Input (see SimpleSchema) + * @returns {Promise} An object with a `method` property containing the updated method + */ +export default async function updateFlatRateFulfillmentMethodMutation(context, input) { + const cleanedInput = inputSchema.clean(input); // add default values and such + inputSchema.validate(cleanedInput); + + const { method: inputMethod, methodId, shopId } = cleanedInput; + const { collections } = context; + const { Fulfillment } = collections; + const method = { ...inputMethod }; + + if (!methodId) throw new ReactionError("invalid-parameter", "Method ID to be updated not provided"); + await context.validatePermissions(`reaction:legacy:fulfillmentMethods:${methodId}`, "update", { shopId }); + + // MongoDB schema still uses `enabled` rather than `isEnabled` + method.enabled = method.isEnabled; + delete method.isEnabled; + + // Hardcoded field, each ff-method plugin has to introduce this field for grouping purpose + // Schema defined as optional=true for backward compatibility + method.fulfillmentMethod = "flatRate"; + + const { matchedCount } = await Fulfillment.updateOne({ + "methods._id": methodId, + shopId + }, { + $set: { + "methods.$": { + ...method, + _id: methodId + } + } + }); + if (matchedCount === 0) throw new ReactionError("not-found", "Not found"); + + inputMethod._id = methodId; + + return { method: inputMethod }; +} diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentRestriction.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentRestriction.js new file mode 100644 index 00000000000..ca75678a3ba --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentRestriction.js @@ -0,0 +1,46 @@ +import SimpleSchema from "simpl-schema"; +import ReactionError from "@reactioncommerce/reaction-error"; +import restrictionSchema from "../util/restrictionSchema.js"; + +const inputSchema = new SimpleSchema({ + restrictionId: String, + restriction: restrictionSchema, + shopId: String +}); + + +/** + * @method updateFlatRateFulfillmentRestrictionMutation + * @summary updates a flat rate fulfillment method + * @param {Object} context - an object containing the per-request state + * @param {Object} input - Input (see SimpleSchema) + * @returns {Promise} An object with a `restriction` property containing the updated method + */ +export default async function updateFlatRateFulfillmentRestrictionMutation(context, input) { + const cleanedInput = inputSchema.clean(input); // add default values and such + inputSchema.validate(cleanedInput); + + const { restriction, restrictionId, shopId } = cleanedInput; + const { collections } = context; + const { FulfillmentRestrictions } = collections; + + await context.validatePermissions(`reaction:legacy:fulfillmentRestrictions:${restrictionId}`, "update", { shopId }); + + const { matchedCount } = await FulfillmentRestrictions.updateOne({ + _id: restrictionId, + shopId + }, { + $set: { + ...restriction + } + }); + if (matchedCount === 0) throw new ReactionError("not-found", "Not found"); + + return { + restriction: { + _id: restrictionId, + shopId, + ...restriction + } + }; +} diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentRestriction.test.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentRestriction.test.js new file mode 100644 index 00000000000..5f300f7afb9 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentRestriction.test.js @@ -0,0 +1,52 @@ +import mockCollection from "@reactioncommerce/api-utils/tests/mockCollection.js"; +import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; +import updateFlatRateFulfillmentRestrictionMutation from "./updateFlatRateFulfillmentRestriction.js"; + + +// Create mock context with FulfillmentRestrictions collection +mockContext.collections.FulfillmentRestrictions = mockCollection("FulfillmentRestrictions"); +mockContext.validatePermissions.mockReturnValueOnce(Promise.resolve(null)); + +const restriction = { + type: "deny", + attributes: [ + { property: "vendor", value: "reaction", propertyType: "string", operator: "eq" }, + { property: "productType", value: "knife", propertyType: "string", operator: "eq" } + ], + destination: { region: ["CO", "NY"] } +}; + +const updatedRestriction = { + type: "deny", + attributes: [ + { property: "vendor", value: "john", propertyType: "string", operator: "eq" }, + { property: "productType", value: "gun", propertyType: "string", operator: "eq" } + ], + destination: { region: ["CO", "NY"] } +}; + +test("update a flat rate fulfillment restriction", async () => { + mockContext.collections.FulfillmentRestrictions.updateOne.mockReturnValueOnce(Promise.resolve({ + ok: 1, + updatedRestriction + })); + + const result = await updateFlatRateFulfillmentRestrictionMutation(mockContext, { + restriction, + restrictionId: "restriction123", + shopId: "shop123" + }); + + expect(result).toEqual({ + restriction: { + _id: "restriction123", + type: "deny", + attributes: [ + { property: "vendor", value: "reaction", propertyType: "string", operator: "eq" }, + { property: "productType", value: "knife", propertyType: "string", operator: "eq" } + ], + destination: { region: ["CO", "NY"] }, + shopId: "shop123" + } + }); +}); diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/policies.json b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/policies.json new file mode 100644 index 00000000000..e48ede9a606 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/policies.json @@ -0,0 +1,55 @@ +[ + { + "description": "Shipping managers acting on all shipping methods.", + "subjects": [ "reaction:groups:shipping-managers" ], + "resources": [ "reaction:legacy:shipping-rates" ], + "actions": [ + "update:settings" + ], + "effect": "allow" + }, + { + "description": "Shipping managers acting on all shipping methods.", + "subjects": [ "reaction:groups:shipping-managers" ], + "resources": [ "reaction:legacy:shippingMethods" ], + "actions": [ + "create", + "read" + ], + "effect": "allow" + }, + { + "description": "Shipping managers acting on a single shipping method.", + "subjects": [ "reaction:groups:shipping-managers" ], + "resources": [ "reaction:legacy:shippingMethods:*" ], + "actions": [ + "create", + "delete", + "read", + "update" + ], + "effect": "allow" + }, + { + "description": "Shipping managers acting on all shipping restrictions.", + "subjects": [ "reaction:groups:shipping-managers" ], + "resources": [ "reaction:legacy:shippingRestrictions" ], + "actions": [ + "create", + "read" + ], + "effect": "allow" + }, + { + "description": "Shipping managers acting on a single shipping restriction.", + "subjects": [ "reaction:groups:shipping-managers" ], + "resources": [ "reaction:legacy:shippingRestrictions:*" ], + "actions": [ + "create", + "delete", + "read", + "update" + ], + "effect": "allow" + } +] diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/preStartup.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/preStartup.js new file mode 100644 index 00000000000..78ea683354c --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/preStartup.js @@ -0,0 +1,27 @@ +import { MethodFlatRateData } from "./simpleSchemas.js"; +/** + * @summary Called on preStartup + * @param {Object} context Startup context + * @returns {undefined} + */ +export default async function fulfillmentMethodShippingFlatRatePreStartup(context) { + const { simpleSchemas: { ShippingMethod, SelectedFulfillmentOption } } = context; + + ShippingMethod.extend({ + methodAdditionalData: { + type: ShippingMethod.getDefinition( + "methodAdditionalData", + ["type"] + ).type[0].type.extend(MethodFlatRateData) + } + }); + + SelectedFulfillmentOption.extend({ + methodAdditionalData: { + type: SelectedFulfillmentOption.getDefinition( + "methodAdditionalData", + ["type"] + ).type[0].type.extend(MethodFlatRateData) + } + }); +} diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/flatRateFulfillmentMethod.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/flatRateFulfillmentMethod.js new file mode 100644 index 00000000000..0f80732b684 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/flatRateFulfillmentMethod.js @@ -0,0 +1,31 @@ +/** + * @name flatRateFulfillmentMethod + * @method + * @memberof Fulfillment/Queries + * @summary Query the Fulfillment collection for a single fulfillment method + * @param {Object} context - an object containing the per-request state + * @param {Object} input - Request input + * @param {String} input.methodId - The fulfillment method id + * @param {String} input.shopId - The shop id of the fulfillment method + * @returns {Promise} Mongo cursor + */ +export default async function flatRateFulfillmentMethod(context, input) { + const { collections } = context; + const { Fulfillment } = collections; + const { methodId, shopId } = input; + + await context.validatePermissions("reaction:legacy:fulfillmentMethods", "read", { shopId }); + + const doc = await Fulfillment.findOne({ + "methods._id": methodId, + shopId + }); + if (!doc) return null; + + // eslint-disable-next-line no-shadow + const method = doc.methods.find((method) => method._id === methodId); + return { + ...method, + shopId + }; +} diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/flatRateFulfillmentMethods.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/flatRateFulfillmentMethods.js new file mode 100644 index 00000000000..076a2de158e --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/flatRateFulfillmentMethods.js @@ -0,0 +1,48 @@ +/** + * @name flatRateFulfillmentMethods + * @method + * @memberof Fulfillment/Queries + * @summary Query the Fulfillment collection for a list of fulfillment methods + * @param {Object} context - an object containing the per-request state + * @param {Object} input - Request input + * @param {String} input.shopId - The shop id of the fulfillment methods + * @returns {Promise} Mongo cursor + */ +export default async function flatRateFulfillmentMethods(context, input) { + const { collections } = context; + const { Fulfillment } = collections; + const { shopId } = input; + const fulfillmentTypeName = "shipping"; + const fulfillmentMethodName = "flatRate"; + + await context.validatePermissions("reaction:legacy:fulfillmentMethods", "read", { shopId }); + + // aggregate pipeline to extract fulfillment methods inside shippment + return { + collection: Fulfillment, + pipeline: [ + { + $match: { + "fulfillmentType": fulfillmentTypeName, + "methods.fulfillmentMethod": fulfillmentMethodName, + shopId + } + }, + { + $unwind: "$methods" + }, + { + $replaceRoot: { + newRoot: { + $mergeObjects: [ + "$methods", + { + shopId: "$$ROOT.shopId" + } + ] + } + } + } + ] + }; +} diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/getFlatRateFulfillmentRestriction.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/getFlatRateFulfillmentRestriction.js new file mode 100644 index 00000000000..19ea42f331b --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/getFlatRateFulfillmentRestriction.js @@ -0,0 +1,21 @@ +/** + * @name getFlatRateFulfillmentRestriction + * @method + * @memberof Fulfillment/NoMeteorQueries + * @summary Query the FulfillmentRestrictions collection for restrictions with the provided shopId + * @param {Object} context - an object containing the per-request state + * @param {Object} params - request parameters + * @param {String} params.shopId - Shop ID for the shop that owns the restriction + * @returns {Promise|undefined} - A restrictions document, if one is found + */ +export default async function getFlatRateFulfillmentRestriction(context, { restrictionId, shopId } = {}) { + const { collections } = context; + const { FulfillmentRestrictions } = collections; + + await context.validatePermissions(`reaction:legacy:fulfillmentRestrictions:${restrictionId}`, "read", { shopId }); + + return FulfillmentRestrictions.findOne({ + _id: restrictionId, + shopId + }); +} diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/getFlatRateFulfillmentRestrictions.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/getFlatRateFulfillmentRestrictions.js new file mode 100644 index 00000000000..116dac58672 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/getFlatRateFulfillmentRestrictions.js @@ -0,0 +1,20 @@ +/** + * @name getFlatRateFulfillmentRestrictions + * @method + * @memberof Fulfillment/NoMeteorQueries + * @summary Query the FulfillmentRestrictions collection for restrictions with the provided shopId + * @param {Object} context - an object containing the per-request state + * @param {Object} params - request parameters + * @param {String} params.shopId - Shop ID for the shop that owns the restrictions + * @returns {Promise|undefined} - A restrictions document, if one is found + */ +export default async function getFlatRateFulfillmentRestrictions(context, { shopId } = {}) { + const { collections } = context; + const { FulfillmentRestrictions } = collections; + + await context.validatePermissions("reaction:legacy:fulfillmentRestrictions", "read", { shopId }); + + return FulfillmentRestrictions.find({ + shopId + }); +} diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/index.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/index.js new file mode 100644 index 00000000000..5cdb840e760 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/index.js @@ -0,0 +1,11 @@ +import flatRateFulfillmentMethod from "./flatRateFulfillmentMethod.js"; +import getFlatRateFulfillmentRestriction from "./getFlatRateFulfillmentRestriction.js"; +import getFlatRateFulfillmentRestrictions from "./getFlatRateFulfillmentRestrictions.js"; +import flatRateFulfillmentMethods from "./flatRateFulfillmentMethods.js"; + +export default { + flatRateFulfillmentMethod, + getFlatRateFulfillmentRestriction, + getFlatRateFulfillmentRestrictions, + flatRateFulfillmentMethods +}; diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/FlatRateFulfillmentMethod/index.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/FlatRateFulfillmentMethod/index.js new file mode 100644 index 00000000000..504f64a218c --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/FlatRateFulfillmentMethod/index.js @@ -0,0 +1,8 @@ +import resolveShopFromShopId from "@reactioncommerce/api-utils/graphql/resolveShopFromShopId.js"; +import { encodeFulfillmentMethodOpaqueId } from "../../xforms/id.js"; + +export default { + _id: (node) => encodeFulfillmentMethodOpaqueId(node._id), + shop: resolveShopFromShopId, + isEnabled: (node) => !!node.enabled +}; diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/FlatRateFulfillmentRestriction/index.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/FlatRateFulfillmentRestriction/index.js new file mode 100644 index 00000000000..5db151f8fa4 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/FlatRateFulfillmentRestriction/index.js @@ -0,0 +1,11 @@ +import { + encodeFulfillmentMethodOpaqueId, + encodeFulfillmentRestrictionOpaqueId, + encodeShopOpaqueId +} from "../../xforms/id.js"; + +export default { + _id: (node) => encodeFulfillmentRestrictionOpaqueId(node._id), + methodIds: (node) => node.methodIds.map(encodeFulfillmentMethodOpaqueId), + shopId: (node) => encodeShopOpaqueId(node.shopId) +}; diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Mutation/createFlatRateFulfillmentMethod.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Mutation/createFlatRateFulfillmentMethod.js new file mode 100644 index 00000000000..8c5914ee9ca --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Mutation/createFlatRateFulfillmentMethod.js @@ -0,0 +1,31 @@ +import { decodeShopOpaqueId } from "../../xforms/id.js"; +import createFlatRateFulfillmentMethodMutation from "../../mutations/createFlatRateFulfillmentMethod.js"; + +/** + * @name Mutation/createFlatRateFulfillmentMethod + * @method + * @memberof Fulfillment/GraphQL + * @summary resolver for the createFlatRateFulfillmentMethod GraphQL mutation + * @param {Object} parentResult - unused + * @param {Object} args.input - an object of all mutation arguments that were sent by the client + * @param {Object} args.input.method - The method object + * @param {String} args.input.shopId - The shop to create this flat rate fulfillment method for + * @param {String} [args.input.clientMutationId] - An optional string identifying the mutation call + * @param {Object} context - an object containing the per-request state + * @returns {Promise} CreateFlatRateFulfillmentMethodPayload + */ +export default async function createFlatRateFulfillmentMethod(parentResult, { input }, context) { + const { clientMutationId = null, method, shopId: opaqueShopId } = input; + + const shopId = decodeShopOpaqueId(opaqueShopId); + + const { method: insertedMethod } = await createFlatRateFulfillmentMethodMutation(context, { + method, + shopId + }); + + return { + clientMutationId, + method: insertedMethod + }; +} diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Mutation/createFlatRateFulfillmentRestriction.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Mutation/createFlatRateFulfillmentRestriction.js new file mode 100644 index 00000000000..aad8aaf7fa8 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Mutation/createFlatRateFulfillmentRestriction.js @@ -0,0 +1,38 @@ +import { decodeFulfillmentMethodOpaqueId, decodeShopOpaqueId } from "../../xforms/id.js"; +import createFlatRateFulfillmentRestrictionMutation from "../../mutations/createFlatRateFulfillmentRestriction.js"; + +/** + * @name Mutation/createFlatRateFulfillmentMethod + * @method + * @memberof Fulfillment/GraphQL + * @summary resolver for the createFlatRateFulfillmentMethod GraphQL mutation + * @param {Object} parentResult - unused + * @param {Object} args.input - an object of all mutation arguments that were sent by the client + * @param {Object} args.input.restriction - The restriction object + * @param {String} args.input.shopId - The shop to create this flat rate fulfillment method for + * @param {String} [args.input.clientMutationId] - An optional string identifying the mutation call + * @param {Object} context - an object containing the per-request state + * @returns {Promise} CreateFlatRateFulfillmentMethodPayload + */ +export default async function createFlatRateFulfillmentRestriction(parentResult, { input }, context) { + const { clientMutationId = null, restriction, shopId: opaqueShopId } = input; + + const shopId = decodeShopOpaqueId(opaqueShopId); + + let decodedMethodIds = []; + if (restriction.methodIds && Array.isArray(restriction.methodIds)) { + decodedMethodIds = restriction.methodIds.map((methodId) => decodeFulfillmentMethodOpaqueId(methodId)); + } + + restriction.methodIds = decodedMethodIds; + + const { restriction: insertedRestriction } = await createFlatRateFulfillmentRestrictionMutation(context, { + restriction, + shopId + }); + + return { + clientMutationId, + restriction: insertedRestriction + }; +} diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Mutation/deleteFlatRateFulfillmentMethod.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Mutation/deleteFlatRateFulfillmentMethod.js new file mode 100644 index 00000000000..92e247119aa --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Mutation/deleteFlatRateFulfillmentMethod.js @@ -0,0 +1,32 @@ +import { decodeFulfillmentMethodOpaqueId, decodeShopOpaqueId } from "../../xforms/id.js"; +import deleteFlatRateFulfillmentMethodMutation from "../../mutations/deleteFlatRateFulfillmentMethod.js"; + +/** + * @name Mutation/deleteFlatRateFulfillmentMethod + * @method + * @memberof Fulfillment/GraphQL + * @summary resolver for the deleteFlatRateFulfillmentMethod GraphQL mutation + * @param {Object} parentResult - unused + * @param {Object} args.input - an object of all mutation arguments that were sent by the client + * @param {String} args.input.methodId - The ID of the method you want to delete + * @param {String} args.input.shopId - The shop to delete this flat rate fulfillment method for + * @param {String} [args.input.clientMutationId] - An optional string identifying the mutation call + * @param {Object} context - an object containing the per-request state + * @returns {Promise} DeleteFlatRateFulfillmentMethodPayload + */ +export default async function deleteFlatRateFulfillmentMethod(parentResult, { input }, context) { + const { clientMutationId = null, methodId: opaqueMethodId, shopId: opaqueShopId } = input; + + const shopId = decodeShopOpaqueId(opaqueShopId); + const methodId = decodeFulfillmentMethodOpaqueId(opaqueMethodId); + + const { method } = await deleteFlatRateFulfillmentMethodMutation(context, { + methodId, + shopId + }); + + return { + clientMutationId, + method + }; +} diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Mutation/deleteFlatRateFulfillmentRestriction.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Mutation/deleteFlatRateFulfillmentRestriction.js new file mode 100644 index 00000000000..0a5c62a92ce --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Mutation/deleteFlatRateFulfillmentRestriction.js @@ -0,0 +1,32 @@ +import { decodeFulfillmentRestrictionOpaqueId, decodeShopOpaqueId } from "../../xforms/id.js"; +import deleteFlatRateFulfillmentRestrictionMutation from "../../mutations/deleteFlatRateFulfillmentRestriction.js"; + +/** + * @name Mutation/deleteFlatRateFulfillmentRestriction + * @method + * @memberof Fulfillment/GraphQL + * @summary resolver for the deleteFlatRateFulfillmentRestriction GraphQL mutation + * @param {Object} parentResult - unused + * @param {Object} args.input - an object of all mutation arguments that were sent by the client + * @param {String} args.input.restrictionId - The ID of the restriction you want to delete + * @param {String} args.input.shopId - The shop to delete this restriction for + * @param {String} [args.input.clientMutationId] - An optional string identifying the mutation call + * @param {Object} context - an object containing the per-request state + * @returns {Promise} DeleteFlatRateFulfillmentRestrictionPayload + */ +export default async function deleteFlatRateFulfillmentRestriction(parentResult, { input }, context) { + const { clientMutationId = null, restrictionId: opaqueRestrictionId, shopId: opaqueShopId } = input; + + const shopId = decodeShopOpaqueId(opaqueShopId); + const restrictionId = decodeFulfillmentRestrictionOpaqueId(opaqueRestrictionId); + + const { restriction } = await deleteFlatRateFulfillmentRestrictionMutation(context, { + restrictionId, + shopId + }); + + return { + clientMutationId, + restriction + }; +} diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Mutation/index.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Mutation/index.js new file mode 100644 index 00000000000..b07a122922c --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Mutation/index.js @@ -0,0 +1,15 @@ +import createFlatRateFulfillmentMethod from "./createFlatRateFulfillmentMethod.js"; +import deleteFlatRateFulfillmentMethod from "./deleteFlatRateFulfillmentMethod.js"; +import updateFlatRateFulfillmentMethod from "./updateFlatRateFulfillmentMethod.js"; +import createFlatRateFulfillmentRestriction from "./createFlatRateFulfillmentRestriction.js"; +import deleteFlatRateFulfillmentRestriction from "./deleteFlatRateFulfillmentRestriction.js"; +import updateFlatRateFulfillmentRestriction from "./updateFlatRateFulfillmentRestriction.js"; + +export default { + createFlatRateFulfillmentMethod, + deleteFlatRateFulfillmentMethod, + updateFlatRateFulfillmentMethod, + createFlatRateFulfillmentRestriction, + deleteFlatRateFulfillmentRestriction, + updateFlatRateFulfillmentRestriction +}; diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Mutation/updateFlatRateFulfillmentMethod.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Mutation/updateFlatRateFulfillmentMethod.js new file mode 100644 index 00000000000..0ae0267effc --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Mutation/updateFlatRateFulfillmentMethod.js @@ -0,0 +1,34 @@ +import { decodeFulfillmentMethodOpaqueId, decodeShopOpaqueId } from "../../xforms/id.js"; +import updateFlatRateFulfillmentMethodMutation from "../../mutations/updateFlatRateFulfillmentMethod.js"; + +/** + * @name Mutation/updateFlatRateFulfillmentMethod + * @method + * @memberof Fulfillment/GraphQL + * @summary resolver for the updateFlatRateFulfillmentMethod GraphQL mutation + * @param {Object} parentResult - unused + * @param {Object} args.input - an object of all mutation arguments that were sent by the client + * @param {String} args.input.methodId - The ID of the method you want to update + * @param {Object} args.input.method - The full updated method object, without ID + * @param {String} args.input.shopId - The shop to update this flat rate fulfillment method for + * @param {String} [args.input.clientMutationId] - An optional string identifying the mutation call + * @param {Object} context - an object containing the per-request state + * @returns {Promise} UpdateFlatRateFulfillmentMethodPayload + */ +export default async function updateFlatRateFulfillmentMethod(parentResult, { input }, context) { + const { clientMutationId = null, method, methodId: opaqueMethodId, shopId: opaqueShopId } = input; + + const shopId = decodeShopOpaqueId(opaqueShopId); + const methodId = decodeFulfillmentMethodOpaqueId(opaqueMethodId); + + const { method: updatedMethod } = await updateFlatRateFulfillmentMethodMutation(context, { + method, + methodId, + shopId + }); + + return { + clientMutationId, + method: updatedMethod + }; +} diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Mutation/updateFlatRateFulfillmentRestriction.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Mutation/updateFlatRateFulfillmentRestriction.js new file mode 100644 index 00000000000..6a6a70a5775 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Mutation/updateFlatRateFulfillmentRestriction.js @@ -0,0 +1,45 @@ +import { + decodeFulfillmentMethodOpaqueId, + decodeFulfillmentRestrictionOpaqueId, + decodeShopOpaqueId +} from "../../xforms/id.js"; +import updateFlatRateFulfillmentRestrictionMutation from "../../mutations/updateFlatRateFulfillmentRestriction.js"; + +/** + * @name Mutation/updateFlatRateFulfillmentRestriction + * @method + * @memberof Fulfillment/GraphQL + * @summary resolver for the updateFlatRateFulfillmentRestriction GraphQL mutation + * @param {Object} parentResult - unused + * @param {Object} args.input - an object of all mutation arguments that were sent by the client + * @param {String} args.input.restrictionId - The ID of the restriction you want to update + * @param {Object} args.input.restriction - The full updated restriction object, without ID + * @param {String} args.input.shopId - The shop to update this flat rate fulfillment restriction for + * @param {String} [args.input.clientMutationId] - An optional string identifying the mutation call + * @param {Object} context - an object containing the per-request state + * @returns {Promise} updateFlatRateFulfillmentRestrictionPayload + */ +export default async function updateFlatRateFulfillmentRestriction(parentResult, { input }, context) { + const { clientMutationId = null, restriction, restrictionId: opaqueRestrictionId, shopId: opaqueShopId } = input; + + const shopId = decodeShopOpaqueId(opaqueShopId); + const restrictionId = decodeFulfillmentRestrictionOpaqueId(opaqueRestrictionId); + + let decodedMethodIds = []; + if (restriction.methodIds && Array.isArray(restriction.methodIds)) { + decodedMethodIds = restriction.methodIds.map((methodId) => decodeFulfillmentMethodOpaqueId(methodId)); + } + + restriction.methodIds = decodedMethodIds; + + const { restriction: updatedRestriction } = await updateFlatRateFulfillmentRestrictionMutation(context, { + restriction, + restrictionId, + shopId + }); + + return { + clientMutationId, + restriction: updatedRestriction + }; +} diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Query/flatRateFulfillmentMethod.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Query/flatRateFulfillmentMethod.js new file mode 100644 index 00000000000..b65aa6523a8 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Query/flatRateFulfillmentMethod.js @@ -0,0 +1,28 @@ +import { decodeFulfillmentMethodOpaqueId, decodeShopOpaqueId } from "../../xforms/id.js"; + +/** + * @name Query/flatRateFulfillmentMethod + * @method + * @memberof Fulfillment/Query + * @summary Query for a single fulfillment method + * @param {Object} _ - unused + * @param {Object} args - an object of all arguments that were sent by the client + * @param {String} args.methodId - Fulfillment method ID to get the record of + * @param {String} args.shopId - Shop ID to get record for + * @param {Object} context - an object containing the per-request state + * @returns {Promise} Fulfillment method + */ +export default async function flatRateFulfillmentMethod(_, args, context) { + const { + methodId: opaqueMethodId, + shopId: opaqueShopId + } = args; + + const methodId = decodeFulfillmentMethodOpaqueId(opaqueMethodId); + const shopId = decodeShopOpaqueId(opaqueShopId); + + return context.queries.flatRateFulfillmentMethod(context, { + methodId, + shopId + }); +} diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Query/flatRateFulfillmentMethods.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Query/flatRateFulfillmentMethods.js new file mode 100644 index 00000000000..113888fae26 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Query/flatRateFulfillmentMethods.js @@ -0,0 +1,31 @@ +import getPaginatedResponseFromAggregate from "@reactioncommerce/api-utils/graphql/getPaginatedResponseFromAggregate.js"; +import wasFieldRequested from "@reactioncommerce/api-utils/graphql/wasFieldRequested.js"; +import { decodeShopOpaqueId } from "../../xforms/id.js"; + +/** + * @name Query/flatRateFulfillmentMethods + * @method + * @memberof Fulfillment/Query + * @summary Query for a list of fulfillment methods + * @param {Object} _ - unused + * @param {Object} args - an object of all arguments that were sent by the client + * @param {String} args.shopId - Shop ID to get records for + * @param {Object} context - an object containing the per-request state + * @param {Object} info Info about the GraphQL request + * @returns {Promise} Fulfillment methods + */ +export default async function flatRateFulfillmentMethods(_, args, context, info) { + const { shopId: opaqueShopId, ...connectionArgs } = args; + + const shopId = decodeShopOpaqueId(opaqueShopId); + + const { collection, pipeline } = await context.queries.flatRateFulfillmentMethods(context, { + shopId + }); + + return getPaginatedResponseFromAggregate(collection, pipeline, connectionArgs, { + includeHasNextPage: wasFieldRequested("pageInfo.hasNextPage", info), + includeHasPreviousPage: wasFieldRequested("pageInfo.hasPreviousPage", info), + includeTotalCount: wasFieldRequested("totalCount", info) + }); +} diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Query/getFlatRateFulfillmentRestriction.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Query/getFlatRateFulfillmentRestriction.js new file mode 100644 index 00000000000..e0955d25650 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Query/getFlatRateFulfillmentRestriction.js @@ -0,0 +1,21 @@ +import { decodeFulfillmentRestrictionOpaqueId, decodeShopOpaqueId } from "../../xforms/id.js"; + +/** + * @name Query/getFlatRateFulfillmentRestriction + * @method + * @memberof Fulfillment/GraphQL + * @summary resolver for the getFlatRateFulfillmentRestriction GraphQL mutation + * @param {Object} parentResult - unused + * @param {ConnectionArgs} args - an object of all arguments that were sent by the client + * @param {String} args.shopId - The shop that owns this restriction + * @param {Object} context - an object containing the per-request state + * @returns {Promise|undefined} A Restriction object + */ +export default async function getFlatRateFulfillmentRestriction(parentResult, args, context) { + const { restrictionId, shopId } = args; + + return context.queries.getFlatRateFulfillmentRestriction(context, { + restrictionId: decodeFulfillmentRestrictionOpaqueId(restrictionId), + shopId: decodeShopOpaqueId(shopId) + }); +} diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Query/getFlatRateFulfillmentRestrictions.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Query/getFlatRateFulfillmentRestrictions.js new file mode 100644 index 00000000000..6f4b8792c3e --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Query/getFlatRateFulfillmentRestrictions.js @@ -0,0 +1,29 @@ +import getPaginatedResponse from "@reactioncommerce/api-utils/graphql/getPaginatedResponse.js"; +import wasFieldRequested from "@reactioncommerce/api-utils/graphql/wasFieldRequested.js"; +import { decodeShopOpaqueId } from "../../xforms/id.js"; + +/** + * @name Query/getFlatRateFulfillmentRestrictions + * @method + * @memberof Fulfillment/GraphQL + * @summary resolver for the getFlatRateFulfillmentRestrictions GraphQL mutation + * @param {Object} parentResult - unused + * @param {Object} args - an object of all arguments that were sent by the client + * @param {String} args.shopId - The shop that owns these restriction + * @param {Object} context - an object containing the per-request state + * @param {Object} info Info about the GraphQL request + * @returns {Promise|undefined} A Restriction object + */ +export default async function getFlatRateFulfillmentRestrictions(parentResult, args, context, info) { + const { shopId, ...connectionArgs } = args; + + const cursor = await context.queries.getFlatRateFulfillmentRestrictions(context, { + shopId: decodeShopOpaqueId(shopId) + }); + + return getPaginatedResponse(cursor, connectionArgs, { + includeHasNextPage: wasFieldRequested("pageInfo.hasNextPage", info), + includeHasPreviousPage: wasFieldRequested("pageInfo.hasPreviousPage", info), + includeTotalCount: wasFieldRequested("totalCount", info) + }); +} diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Query/index.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Query/index.js new file mode 100644 index 00000000000..cda11a395d0 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Query/index.js @@ -0,0 +1,11 @@ +import flatRateFulfillmentMethod from "./flatRateFulfillmentMethod.js"; +import flatRateFulfillmentMethods from "./flatRateFulfillmentMethods.js"; +import getFlatRateFulfillmentRestriction from "./getFlatRateFulfillmentRestriction.js"; +import getFlatRateFulfillmentRestrictions from "./getFlatRateFulfillmentRestrictions.js"; + +export default { + flatRateFulfillmentMethod, + flatRateFulfillmentMethods, + getFlatRateFulfillmentRestriction, + getFlatRateFulfillmentRestrictions +}; diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/ShopSettings/index.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/ShopSettings/index.js new file mode 100644 index 00000000000..fcb55cf288b --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/ShopSettings/index.js @@ -0,0 +1,9 @@ +// ShopSettings are public by default. Here we add a permission check. +export default { + async isShippingRatesFulfillmentEnabled(settings, args, context) { + await context.validatePermissions("reaction:legacy:fulfillment", "read", { + shopId: settings.shopId + }); + return settings.isShippingRatesFulfillmentEnabled; + } +}; diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/index.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/index.js new file mode 100644 index 00000000000..44ecb282069 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/index.js @@ -0,0 +1,16 @@ +import getConnectionTypeResolvers from "@reactioncommerce/api-utils/graphql/getConnectionTypeResolvers.js"; +import FlatRateFulfillmentMethod from "./FlatRateFulfillmentMethod/index.js"; +import FlatRateFulfillmentRestriction from "./FlatRateFulfillmentRestriction/index.js"; +import Mutation from "./Mutation/index.js"; +import Query from "./Query/index.js"; +import ShopSettings from "./ShopSettings/index.js"; + +export default { + ...getConnectionTypeResolvers("FlatRateFulfillmentRestriction"), + ...getConnectionTypeResolvers("FlatRateFulfillmentMethod"), + FlatRateFulfillmentMethod, + FlatRateFulfillmentRestriction, + Mutation, + Query, + ShopSettings +}; diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/schemas/index.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/schemas/index.js new file mode 100644 index 00000000000..d994138d159 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/schemas/index.js @@ -0,0 +1,6 @@ +import importAsString from "@reactioncommerce/api-utils/importAsString.js"; + +const schema = importAsString("./schema.graphql"); +const restrictions = importAsString("./restrictions.graphql"); + +export default [schema, restrictions]; diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/schemas/restrictions.graphql b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/schemas/restrictions.graphql new file mode 100644 index 00000000000..ca718216d18 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/schemas/restrictions.graphql @@ -0,0 +1,297 @@ +#### +# Custom data types for FlatRateFulfillmentRestriction data +#### +"An attribute restriction condition" +type AttributeRestrictions { + "The operator to use for value comparison" + operator: String! + + "The property to check" + property: String! + + "The type of this property" + propertyType: String! + + "The value to check for" + value: String! +} + +""" +Destination restriction conditions. If multiple of `country`, +`region`, and `postal` are set, there is an AND relationship. +""" +type DestinationRestrictions { + "Restrict for any of these destination countries" + country: [String] + + "Restrict for any of these destination postal codes" + postal: [String] + + "Restrict for any of these destination regions" + region: [String] +} + +"Restriction type" +enum RestrictionTypeEnum { + "Allow" + allow + + "Deny" + deny +} + +"Allowed values for `FlatRateFulfillmentRestriction` sortBy parameter" +enum FlatRateFulfillmentRestrictionSortByField { + "Date the restriction was created" + createdAt +} + +"Input to create an attribute restriction condition" +input AttributeRestrictionsInput { + "The operator to use for value comparison" + operator: String! + + "The property to check" + property: String! + + "The type of this property" + propertyType: String! + + "The value to check for" + value: String! +} + +"Input for a destination restriction condition" +input DestinationRestrictionsInput { + "Restrict for any of these destination countries" + country: [String] + + "Restrict for any of these destination postal codes" + postal: [String] + + "Restrict for any of these destination regions" + region: [String] +} + +#### +# Queries +#### +extend type Query { + "Get the full list of flat rate fulfillment method restrictions." + getFlatRateFulfillmentRestrictions( + "Shop to get restrictions for" + shopId: ID!, + + "Return only results that come after this cursor. Use this with `first` to specify the number of results to return." + after: ConnectionCursor, + + "Return only results that come before this cursor. Use this with `last` to specify the number of results to return." + before: ConnectionCursor, + + "Return at most this many results. This parameter may be used with either `after` or `offset` parameters." + first: ConnectionLimitInt, + + "Return at most this many results. This parameter may be used with the `before` parameter." + last: ConnectionLimitInt, + + "Return only results that come after the Nth result. This parameter may be used with the `first` parameter." + offset: Int, + + "Return results sorted in this order" + sortOrder: SortOrder = desc, + + "By default, restrictions are sorted by when they were created, newest first. Set this to sort by one of the other allowed fields" + sortBy: FlatRateFulfillmentRestrictionSortByField = createdAt + ): FlatRateFulfillmentRestrictionConnection! + + "Get a single flat rate fulfillment method restriction." + getFlatRateFulfillmentRestriction( + "The restriction ID" + restrictionId: ID!, + + "Shop that owns the restriction" + shopId: ID! + ): FlatRateFulfillmentRestriction +} + +""" +Wraps a list of `FlatRateFulfillmentRestriction`s, providing pagination cursors and information. + +For information about what Relay-compatible connections are and how to use them, see the following articles: +- [Relay Connection Documentation](https://facebook.github.io/relay/docs/en/graphql-server-specification.html#connections) +- [Relay Connection Specification](https://facebook.github.io/relay/graphql/connections.htm) +- [Using Relay-style Connections With Apollo Client](https://www.apollographql.com/docs/react/recipes/pagination.html) +""" +type FlatRateFulfillmentRestrictionConnection { + "The list of nodes that match the query, wrapped in an edge to provide a cursor string for each" + edges: [FlatRateFulfillmentRestrictionEdge] + + """ + You can request the `nodes` directly to avoid the extra wrapping that `NodeEdge` has, + if you know you will not need to paginate the results. + """ + nodes: [FlatRateFulfillmentRestriction] + + "Information to help a client request the next or previous page" + pageInfo: PageInfo! + + "The total number of nodes that match your query" + totalCount: Int! +} + +"A connection edge in which each node is a `FlatRateFulfillmentRestriction` object" +type FlatRateFulfillmentRestrictionEdge { + "The cursor that represents this node in the paginated results" + cursor: ConnectionCursor! + + "The flat rate fulfillment restriction" + node: FlatRateFulfillmentRestriction +} + +"Defines a flat rate fulfillment method restriction." +type FlatRateFulfillmentRestriction implements Node { + "The restriction ID." + _id: ID! + + "Attribute restrictions. Multiple attribute restrictions are evaluated with AND. If both destination and attribute restrictions are present, they evaluate with AND." + attributes: [AttributeRestrictions] + + "Destination restrictions. If multiple destination restrictions are present, the most localized is the only one evaluated (i.e. evaluate postal if present, then region if present, then country). If both destination and attribute restrictions are present, they evaluate with AND." + destination: DestinationRestrictions + + "Method IDs to apply this restriction to. If none, applies to all methods as a universal restriction." + methodIds: [ID] + + "The shop ID" + shopId: ID! + + """ + The type of this restriction. Allowed types are `allow` or `deny`. + """ + type: RestrictionTypeEnum! +} + +#### +# Create restriction mutation +#### +extend type Mutation { + "Create a flat rate fulfillment method restriction." + createFlatRateFulfillmentRestriction( + "Mutation input" + input: CreateFlatRateFulfillmentRestrictionInput! + ): CreateFlatRateFulfillmentRestrictionPayload! +} + +# Inputs +"Defines the input for a flat rate fulfillment method restriction." +input FlatRateFulfillmentRestrictionInput { + "Attribute restrictions. Multiple attribute restrictions are evaluated with AND. If both destination and attribute restrictions are present, they evaluate with AND." + attributes: [AttributeRestrictionsInput] + + "Destination restrictions. If multiple destination restrictions are present, the most localized is the only one evaluated (i.e. evaluate postal if present, then region if present, then country). If both destination and attribute restrictions are present, they evaluate with AND." + destination: DestinationRestrictionsInput + + "Method IDs to apply this restriction to. If none, applies to all methods as a universal restriction." + methodIds: [ID] + + """ + The type of this restriction. Allowed types are `allow` or `deny`. + """ + type: RestrictionTypeEnum! +} + +"Input for the `CreateFlatRateFulfillmentRestriction` mutation" +input CreateFlatRateFulfillmentRestrictionInput { + "An optional string identifying the mutation call, which will be returned in the response payload" + clientMutationId: String + + "This defines the flat rate fulfillment method restriction that you want to create" + restriction: FlatRateFulfillmentRestrictionInput! + + "The shop to create this flat rate fulfillment method restriction for" + shopId: ID! +} + + +# Payloads +"Response from the `CreateFlatRateFulfillmentRestriction` mutation" +type CreateFlatRateFulfillmentRestrictionPayload { + "The same string you sent with the mutation params, for matching mutation calls with their responses" + clientMutationId: String + + "The created flat rate fulfillment method restriction" + restriction: FlatRateFulfillmentRestriction! +} + + +#### +# Delete restriction mutation +#### +extend type Mutation { + "Delete a flat rate fulfillment method restriction" + deleteFlatRateFulfillmentRestriction( + "Mutation input" + input: DeleteFlatRateFulfillmentRestrictionInput! + ): DeleteFlatRateFulfillmentRestrictionPayload! +} + +# Inputs +"Input for the `deleteFlatRateFulfillmentRestriction` mutation" +input DeleteFlatRateFulfillmentRestrictionInput { + "An optional string identifying the mutation call, which will be returned in the response payload" + clientMutationId: String + + "The ID of the flat rate fulfillment method restriction you want to delete" + restrictionId: ID! + + "The shop that owns the flat rate fulfillment method restriction" + shopId: ID! +} + +# Payloads +"Response from the `deleteFlatRateFulfillmentRestriction` mutation" +type DeleteFlatRateFulfillmentRestrictionPayload { + "The same string you sent with the mutation params, for matching mutation calls with their responses" + clientMutationId: String + + "The removed flat rate fulfillment method restriction" + restriction: FlatRateFulfillmentRestriction! +} + + +#### +# Update restriction mutation +#### +extend type Mutation { + "Update a flat rate fulfillment method restriction" + updateFlatRateFulfillmentRestriction( + "Mutation input" + input: UpdateFlatRateFulfillmentRestrictionInput! + ): UpdateFlatRateFulfillmentRestrictionPayload! +} + +# Inputs +"Input for the `updateFlatRateFulfillmentRestriction` mutation" +input UpdateFlatRateFulfillmentRestrictionInput { + "An optional string identifying the mutation call, which will be returned in the response payload" + clientMutationId: String + + "The updated flat rate fulfillment method restriction. Pass the whole updated restriction object without the ID." + restriction: FlatRateFulfillmentRestrictionInput! + + "The ID of the flat rate fulfillment method restriction you want to update" + restrictionId: ID! + + "The shop that owns the flat rate fulfillment method restriction" + shopId: ID! +} + +# Payloads +"Response from the `updateFlatRateFulfillmentMethod` mutation" +type UpdateFlatRateFulfillmentRestrictionPayload { + "The same string you sent with the mutation params, for matching mutation calls with their responses" + clientMutationId: String + + "The updated flat rate fulfillment method restriction" + restriction: FlatRateFulfillmentRestriction! +} diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/schemas/schema.graphql b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/schemas/schema.graphql new file mode 100644 index 00000000000..1ddbd2c72b8 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/schemas/schema.graphql @@ -0,0 +1,264 @@ +#### +# Base +#### + +"Additional data from Flat-rate" +type flatRateData { + gqlType: String + flatRateData: Int +} + +extend union AdditionalData = flatRateData + + +extend type Query { + "Get a flat rate fulfillment method" + flatRateFulfillmentMethod( + "Fulfillment method id" + methodId: ID! + + "Shop ID" + shopId: ID! + ): FlatRateFulfillmentMethod! + + "Get a flat rate fulfillment methods" + flatRateFulfillmentMethods( + "Shop ID" + shopId: ID! + + "Return only results that come after this cursor. Use this with `first` to specify the number of results to return." + after: ConnectionCursor, + + "Return only results that come before this cursor. Use this with `last` to specify the number of results to return." + before: ConnectionCursor, + + "Return at most this many results. This parameter may be used with either `after` or `offset` parameters." + first: ConnectionLimitInt, + + "Return at most this many results. This parameter may be used with the `before` parameter." + last: ConnectionLimitInt, + + "Return only results that come after the Nth result. This parameter may be used with the `first` parameter." + offset: Int, + ): FlatRateFulfillmentMethodConnection! +} + +extend type Mutation { + "Create a flat rate fulfillment method" + createFlatRateFulfillmentMethod( + "Mutation input" + input: CreateFlatRateFulfillmentMethodInput! + ): CreateFlatRateFulfillmentMethodPayload! + + "Update a flat rate fulfillment method" + updateFlatRateFulfillmentMethod( + "Mutation input" + input: UpdateFlatRateFulfillmentMethodInput! + ): UpdateFlatRateFulfillmentMethodPayload! + + "Delete a flat rate fulfillment method" + deleteFlatRateFulfillmentMethod( + "Mutation input" + input: DeleteFlatRateFulfillmentMethodInput! + ): DeleteFlatRateFulfillmentMethodPayload! +} + +"Defines a fulfillment method that has a fixed price. This type is provided by the `flat-rate` fulfillment plugin." +type FlatRateFulfillmentMethod implements Node { + "The flat rate fulfillment method ID" + _id: ID! + + "The cost of this fulfillment method to the shop, if you track this" + cost: Float + + """ + The fulfillment types for which this method may be used. For example, `shipping` or `digital`. + """ + fulfillmentTypes: [FulfillmentType]! + + "The group to which this method belongs" + group: String! + + "A fixed price to charge for handling costs when this fulfillment method is selected for an order" + handling: Float! + + "Include this as a fulfillment option shown to shoppers during checkout?" + isEnabled: Boolean! + + "The name of this method, for display in the user interface" + label: String! + + "The name of this method, a unique identifier" + name: String! + + "The common-name for this method, to group all variants of the same method" + fulfillmentMethod: String + + "A fixed price to charge for fulfillment costs when this fulfillment method is selected for an order" + rate: Float! + + "The shop to which this fulfillment method belongs" + shop: Shop! +} + +#### +# Inputs +#### + +"Defines a fulfillment method that has a fixed price. This type is provided by the `flat-rate` fulfillment plugin." +input FlatRateFulfillmentMethodInput { + "The cost of this fulfillment method to the shop, if you track this" + cost: Float + + """ + The fulfillment types for which this method may be used. For example, `shipping` or `digital`. + """ + fulfillmentTypes: [FulfillmentType]! + + "The group to which this method belongs" + group: String! + + "A fixed price to charge for handling costs when this fulfillment method is selected for an order" + handling: Float! + + "Include this as a fulfillment option shown to shoppers during checkout?" + isEnabled: Boolean! + + "The name of this method, for display in the user interface" + label: String! + + "The name of this method, a unique identifier" + name: String! + + "The common-name for this method, to group all variants of the same method" + fulfillmentMethod: String + + "A fixed price to charge for fulfillment costs when this fulfillment method is selected for an order" + rate: Float! +} + +"Input for the `createFlatRateFulfillmentMethod` mutation" +input CreateFlatRateFulfillmentMethodInput { + "An optional string identifying the mutation call, which will be returned in the response payload" + clientMutationId: String + + "This defines the flat rate fulfillment method that you want to create" + method: FlatRateFulfillmentMethodInput! + + "The shop to create this flat rate fulfillment method for" + shopId: ID! +} + +"Input for the `updateFlatRateFulfillmentMethod` mutation" +input UpdateFlatRateFulfillmentMethodInput { + "An optional string identifying the mutation call, which will be returned in the response payload" + clientMutationId: String + + "The updated method. Pass the whole updated method object without the ID." + method: FlatRateFulfillmentMethodInput! + + "The ID of the flat rate fulfillment method you want to update" + methodId: ID! + + "The shop that owns the method" + shopId: ID! +} + +"Input for the `deleteFlatRateFulfillmentMethod` mutation" +input DeleteFlatRateFulfillmentMethodInput { + "An optional string identifying the mutation call, which will be returned in the response payload" + clientMutationId: String + + "The ID of the flat rate fulfillment method you want to delete" + methodId: ID! + + "The shop that owns the method" + shopId: ID! +} + +#### +# Payloads +#### + +"Response from the `createFlatRateFulfillmentMethod` mutation" +type CreateFlatRateFulfillmentMethodPayload { + "The same string you sent with the mutation params, for matching mutation calls with their responses" + clientMutationId: String + + "The created fulfillment method" + method: FlatRateFulfillmentMethod! +} + +"Response from the `updateFlatRateFulfillmentMethod` mutation" +type UpdateFlatRateFulfillmentMethodPayload { + "The same string you sent with the mutation params, for matching mutation calls with their responses" + clientMutationId: String + + "The updated fulfillment method" + method: FlatRateFulfillmentMethod! +} + +"Response from the `deleteFlatRateFulfillmentMethod` mutation" +type DeleteFlatRateFulfillmentMethodPayload { + "The same string you sent with the mutation params, for matching mutation calls with their responses" + clientMutationId: String + + "The removed fulfillment method" + method: FlatRateFulfillmentMethod! +} + +#### +# Connections +#### + +"A connection edge in which each node is a `FlatRateFulfillmentMethod` object" +type FlatRateFulfillmentMethodEdge { + "The cursor that represents this node in the paginated results" + cursor: ConnectionCursor! + + "The fulfillment method" + node: FlatRateFulfillmentMethod +} + +""" +Wraps a list of FlatRateFulfillmentMethods`s, providing pagination cursors and information. + +For information about what Relay-compatible connections are and how to use them, see the following articles: +- [Relay Connection Documentation](https://facebook.github.io/relay/docs/en/graphql-server-specification.html#connections) +- [Relay Connection Specification](https://facebook.github.io/relay/graphql/connections.htm) +- [Using Relay-style Connections With Apollo Client](https://www.apollographql.com/docs/react/recipes/pagination.html) +""" +type FlatRateFulfillmentMethodConnection { + "The list of nodes that match the query, wrapped in an edge to provide a cursor string for each" + edges: [FlatRateFulfillmentMethodEdge] + + """ + You can request the `nodes` directly to avoid the extra wrapping that `NodeEdge` has, + if you know you will not need to paginate the results. + """ + nodes: [FlatRateFulfillmentMethod] + + "Information to help a client request the next or previous page" + pageInfo: PageInfo! + + "The total number of nodes that match your query" + totalCount: Int! +} + +extend type ShopSettings { + """ + If `false` no defined shipping rates will be used when fulfillment + quotes are requested for a cart or order. A quick way to disable the entire + `reaction-shipping-rates` plugin temporarily. + """ + isShippingRatesFulfillmentEnabled: Boolean +} + +extend input ShopSettingsUpdates { + """ + Set to `false` to prevent any defined shipping rates from being used when fulfillment + quotes are requested for a cart or order. A quick way to disable the entire + `reaction-shipping-rates` plugin temporarily. + """ + isShippingRatesFulfillmentEnabled: Boolean +} diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/simpleSchemas.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/simpleSchemas.js new file mode 100644 index 00000000000..e29df9a7541 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/simpleSchemas.js @@ -0,0 +1,17 @@ +import SimpleSchema from "simpl-schema"; + +/** + * @name MethodFlatRateData + * @memberof Schemas + * @type {SimpleSchema} + * @summary Defines FlatRate additional data + * @property {String} gqlType Defines the method type + * @property {Number} flatRateData FlatRate Data fields + */ +export const MethodFlatRateData = new SimpleSchema({ + gqlType: String, + flatRateData: { + type: Number, + optional: true + } +}); diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/attributeDenyCheck.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/attributeDenyCheck.js new file mode 100644 index 00000000000..93be1ca8aea --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/attributeDenyCheck.js @@ -0,0 +1,48 @@ +import operators from "@reactioncommerce/api-utils/operators.js"; +import propertyTypes from "@reactioncommerce/api-utils/propertyTypes.js"; +import isDestinationRestricted from "./isDestinationRestricted.js"; + +/** + * @summary Filter shipping methods based on per method deny attribute restrictions + * @param {Object} methodRestrictions - method restrictions from FlatRateFulfillmentRestrcitionsCollection + * @param {Object} method - current method to check restrictions against + * @param {Object} hydratedOrder - hydrated order for current order + * @returns {Bool} true / false as to whether method is still valid after this check + */ +export async function attributeDenyCheck(methodRestrictions, method, hydratedOrder) { + // Get method specific attribute deny restrictions + const attributesDenyRestrictions = methodRestrictions.filter((restriction) => restriction.type === "deny" && Array.isArray(restriction.attributes)); + + // If there are no attributes deny restrictions, this method is valid at this point + if (attributesDenyRestrictions.length === 0) return true; + + const { items, shippingAddress } = hydratedOrder; + + const denyMethod = items.some((item) => { // eslint-disable-line + // For each item, run through the restrictions + return attributesDenyRestrictions.some((methodRestriction) => { + const { attributes, destination } = methodRestriction; + + // Item must meet all attributes to be restricted + return attributes.every((attribute) => { + let attributeFound = operators[attribute.operator](item[attribute.property], propertyTypes[attribute.propertyType](attribute.value)); + + // If attribute is an array on the item, use `includes` instead of checking for === + // This works for tags, tagIds, and any future attribute that might be an array + if (Array.isArray(item[attribute.property])) { + attributeFound = item[attribute.property].includes(attribute.value); + } + + if (attributeFound) { + // If there is no destination restriction, destination restriction is global + return !destination || isDestinationRestricted(destination, shippingAddress); + } + + // If shipping location does not match restricted location && attribute, method is not restricted + return false; + }); + }); + }); + + return !denyMethod; +} diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/filterShippingMethods.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/filterShippingMethods.js new file mode 100644 index 00000000000..7a14b99d496 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/filterShippingMethods.js @@ -0,0 +1,48 @@ +import { attributeDenyCheck } from "./attributeDenyCheck.js"; +import { locationAllowCheck } from "./locationAllowCheck.js"; +import { locationDenyCheck } from "./locationDenyCheck.js"; + + +/** + * @summary Filter shipping methods based on per method restrictions + * @param {Object} context - an object containing the per-request state + * @param {Object} methods - all available shipping methods for a shop + * @param {Object} hydratedOrder - hydrated order for current order + * @returns {Object|null} available shipping methods after filtering + */ +export default async function filterShippingMethods(context, methods, hydratedOrder) { + const { FulfillmentRestrictions } = context.collections; + + const allValidShippingMethods = methods.reduce(async (validShippingMethods, method) => { + const awaitedValidShippingMethods = await validShippingMethods; + + // If method is not enabled, it is not valid + // if (!method.isEnabled) { + if (!method.enabled) { + return awaitedValidShippingMethods; + } + + // Find all restrictions for this shipping method + // TODO This 'find' query may need to be fine tuned further to accomodate new/other fulfillment-types + // which could have restriction entries. The new entries would be part of phase-2 + const methodRestrictions = await FulfillmentRestrictions.find({ methodIds: method._id }).toArray(); + // Check method against location allow check + const methodIsAllowedBasedOnShippingLocationsAllowList = await locationAllowCheck(methodRestrictions, method, hydratedOrder); + if (!methodIsAllowedBasedOnShippingLocationsAllowList) return awaitedValidShippingMethods; + + // Check method against location deny check + const methodIsAllowedBasedOnShippingLocationsDenyList = await locationDenyCheck(methodRestrictions, method, hydratedOrder); + if (!methodIsAllowedBasedOnShippingLocationsDenyList) return awaitedValidShippingMethods; + + // Check method against attributes deny check + const methodIsAllowedBasedOnShippingAttributesDenyList = await attributeDenyCheck(methodRestrictions, method, hydratedOrder); + if (!methodIsAllowedBasedOnShippingAttributesDenyList) return awaitedValidShippingMethods; + + // If method passes all checks, it is valid and should be added to valid methods array + awaitedValidShippingMethods.push(method); + return awaitedValidShippingMethods; + }, Promise.resolve([])); + + // Return all valid shipping rates + return allValidShippingMethods; +} diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/filterShippingMethods.test.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/filterShippingMethods.test.js new file mode 100644 index 00000000000..682622f7c83 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/filterShippingMethods.test.js @@ -0,0 +1,816 @@ +import mockCollection from "@reactioncommerce/api-utils/tests/mockCollection.js"; +import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; +import filterShippingMethods from "./filterShippingMethods.js"; + +// Create mock context with FulfillmentRestrictions collection +mockContext.collections.FulfillmentRestrictions = mockCollection("FulfillmentRestrictions"); + +// Mock shipping method +const mockShippingMethod = [ + { + cost: 7.99, + fulfillmentTypes: [ + "shipping" + ], + group: "Ground", + handling: 0, + label: "Ground", + name: "Ground", + rate: 7.99, + _id: "stviZaLdqRvTKW6J5", + enabled: true, + code: "001" + } +]; + +// Mock cart items +const mockHydratedOrderItems = { + _id: "tMkp5QwZog5ihYTfG", + createdAt: "2018-11-01T16:42:03.448Z", + description: "Represent the city that never sleeps with this classic T.", + isDeleted: false, + isTaxable: true, + isVisible: true, + pageTitle: "212. 646. 917.", + price: 12.99, + primaryImage: [Object], + productId: "cR6LKN5yGSiei7cia", + shopId: "J8Bhq3uTtdgwZx3rz", + slug: "new-york-city-1998-t-shirt", + supportedFulfillmentTypes: [Array], + tagIds: [Array], + title: "Small", + type: "product-simple", + updatedAt: "2018-11-01T16:42:03.448Z", + vendor: "Restricted Vendor", + height: 10, + index: 0, + length: 10, + optionTitle: "Small", + originCountry: "US", + taxCode: "0000", + variantId: "tMkp5QwZog5ihYTfG", + weight: 50, + width: 10, + tags: [Array] +}; + +const mockHydratedOrder = { + shippingAddress: + { + address1: "123 California Street", + city: "Los Angeles", + country: "US", + postal: "90405", + region: "CA" + }, + discountTotal: 0, + items: + [mockHydratedOrderItems], + itemTotal: 898.95, + total: 898.95 +}; + +/* + * Tests with location restrictions that allow method + */ +test("allow method - country on allow list, region / zip not on deny list, no item restrictions", async () => { + // Shipping Location: US, CA, 90405 + + const mockMethodRestrictions = [ + { + _id: "allow001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "allow", + destination: { + country: [ + "US" + ] + } + }, + { + _id: "deny001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "deny", + destination: { + region: [ + "AK", + "HI" + ] + } + } + ]; + + mockContext.collections.FulfillmentRestrictions.toArray.mockReturnValue(Promise.resolve(mockMethodRestrictions)); + + const allowedMethods = await filterShippingMethods(mockContext, mockShippingMethod, mockHydratedOrder); + + expect(allowedMethods).toEqual(mockShippingMethod); +}); + +test("allow method - region on allow list, country / zip not on deny list, no item restrictions", async () => { + // Shipping Location: US, CA, 90405 + + const mockMethodRestrictions = [ + { + _id: "allow001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "allow", + destination: { + region: [ + "CA" + ] + } + }, + { + _id: "deny001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "deny", + destination: {} + } + ]; + + mockContext.collections.FulfillmentRestrictions.toArray.mockReturnValue(Promise.resolve(mockMethodRestrictions)); + + const allowedMethods = await filterShippingMethods(mockContext, mockShippingMethod, mockHydratedOrder); + + expect(allowedMethods).toEqual(mockShippingMethod); +}); + +test("allow method - postal on allow list, country / region not on deny list, no item restrictions", async () => { + // Shipping Location: US, CA, 90405 + + const mockMethodRestrictions = [ + { + _id: "allow001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "allow", + destination: { + postal: [ + "90405" + ] + } + }, + { + _id: "deny001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "deny", + destination: {} + } + ]; + + mockContext.collections.FulfillmentRestrictions.toArray.mockReturnValue(Promise.resolve(mockMethodRestrictions)); + + const allowedMethods = await filterShippingMethods(mockContext, mockShippingMethod, mockHydratedOrder); + + expect(allowedMethods).toEqual(mockShippingMethod); +}); + +/* + * Tests with location restrictions AND attribute restrictions that allow method + * Test should pass because Shipping location is not the restricted location + */ +test("allow method - do not allow shipping of `Restricted Vendor` to Canada, all other locations allowed", async () => { + // Shipping Location: US, CA, 90405 + const mockMethodRestrictions = [ + { + _id: "allow001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "allow", + destination: { + country: [ + "US" + ] + } + }, + { + _id: "deny001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "deny", + attributes: [ + { + property: "vendor", + value: "Restricted Vendor", + propertyType: "string", + operator: "eq" + } + ], + destination: { + country: [ + "CA" + ] + } + } + ]; + + mockContext.collections.FulfillmentRestrictions.toArray.mockReturnValue(Promise.resolve(mockMethodRestrictions)); + + const allowedMethods = await filterShippingMethods(mockContext, mockShippingMethod, mockHydratedOrder); + + expect(allowedMethods).toEqual(mockShippingMethod); +}); + +test("allow method - do not allow shipping of `Restricted Vendor` to Hawaii, all other locations allowed", async () => { + // Shipping Location: US, CA, 90405 + + const mockMethodRestrictions = [ + { + _id: "allow001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "allow", + destination: { + country: [ + "US" + ] + } + }, + { + _id: "deny001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "deny", + attributes: [ + { + property: "vendor", + value: "Restricted Vendor", + propertyType: "string", + operator: "eq" + } + ], + destination: { + region: [ + "HI" + ] + } + } + ]; + + mockContext.collections.FulfillmentRestrictions.toArray.mockReturnValue(Promise.resolve(mockMethodRestrictions)); + + const allowedMethods = await filterShippingMethods(mockContext, mockShippingMethod, mockHydratedOrder); + + expect(allowedMethods).toEqual(mockShippingMethod); +}); + +test("allow method - do not allow shipping of `Restricted Vendor` to 10001, all other locations allowed", async () => { + // Shipping Location: US, CA, 90405 + + const mockMethodRestrictions = [ + { + _id: "allow001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "allow", + destination: { + country: [ + "US" + ] + } + }, + { + _id: "deny001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "deny", + attributes: [ + { + property: "vendor", + value: "Restricted Vendor", + propertyType: "string", + operator: "eq" + } + ], + destination: { + postal: [ + "10001" + ] + } + } + ]; + + mockContext.collections.FulfillmentRestrictions.toArray.mockReturnValue(Promise.resolve(mockMethodRestrictions)); + + const allowedMethods = await filterShippingMethods(mockContext, mockShippingMethod, mockHydratedOrder); + + expect(allowedMethods).toEqual(mockShippingMethod); +}); + +test("allow method - multiple attributes but only 1 meets criteria to deny", async () => { + const mockMethodRestrictions = [ + { + _id: "allow001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "allow", + destination: { + country: [ + "US" + ] + } + }, + { + _id: "deny001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "deny", + attributes: [ + { + property: "total", + value: 500, + propertyType: "int", + operator: "lt" + }, + { + property: "weight", + value: 40, + propertyType: "int", + operator: "gt" + } + ] + } + ]; + + mockContext.collections.FulfillmentRestrictions.toArray.mockReturnValue(Promise.resolve(mockMethodRestrictions)); + + const allowedMethods = await filterShippingMethods(mockContext, mockShippingMethod, mockHydratedOrder); + + expect(allowedMethods).toEqual(mockShippingMethod); +}); + + +/* + * Tests with location restrictions AND attribute restrictions that deny method + * Test should fail because Shipping location is the restricted location + */ +test("deny method - do not allow shipping of `Restricted Vendor` to United States, all other locations allowed", async () => { + // Shipping Location: US, CA, 90405 + const mockMethodRestrictions = [ + { + _id: "allow001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "allow", + destination: {} + }, + { + _id: "deny001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "deny", + attributes: [ + { + property: "vendor", + value: "Restricted Vendor", + propertyType: "string", + operator: "eq" + } + ], + destination: { + country: [ + "US" + ] + } + } + ]; + + mockContext.collections.FulfillmentRestrictions.toArray.mockReturnValue(Promise.resolve(mockMethodRestrictions)); + + const allowedMethods = await filterShippingMethods(mockContext, mockShippingMethod, mockHydratedOrder); + + expect(allowedMethods).toEqual([]); +}); + +test("deny method - do not allow shipping of `Restricted Vendor` to California, all other locations allowed", async () => { + // Shipping Location: US, CA, 90405 + + const mockMethodRestrictions = [ + { + _id: "allow001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "allow", + destination: { + country: [ + "US" + ] + } + }, + { + _id: "deny001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "deny", + attributes: [ + { + property: "vendor", + value: "Restricted Vendor", + propertyType: "string", + operator: "eq" + } + ], + destination: { + region: [ + "CA" + ] + } + } + ]; + + mockContext.collections.FulfillmentRestrictions.toArray.mockReturnValue(Promise.resolve(mockMethodRestrictions)); + + const allowedMethods = await filterShippingMethods(mockContext, mockShippingMethod, mockHydratedOrder); + + expect(allowedMethods).toEqual([]); +}); + +test("deny method - do not allow shipping of `Restricted Vendor` to 90405, all other locations allowed", async () => { + // Shipping Location: US, CA, 90405 + + const mockMethodRestrictions = [ + { + _id: "allow001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "allow", + destination: { + country: [ + "US" + ] + } + }, + { + _id: "deny001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "deny", + attributes: [ + { + property: "vendor", + value: "Restricted Vendor", + propertyType: "string", + operator: "eq" + } + ], + destination: { + postal: [ + "90405" + ] + } + } + ]; + + mockContext.collections.FulfillmentRestrictions.toArray.mockReturnValue(Promise.resolve(mockMethodRestrictions)); + + const allowedMethods = await filterShippingMethods(mockContext, mockShippingMethod, mockHydratedOrder); + + expect(allowedMethods).toEqual([]); +}); + +/* + * Tests with location restrictions that deny method + */ +test("deny method - country on deny list, no item restrictions", async () => { + // Shipping Location: US, CA, 90405 + + const mockMethodRestrictions = [ + { + _id: "allow001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "allow", + destination: {} + }, + { + _id: "deny001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "deny", + destination: { + country: [ + "US" + ] + } + } + ]; + + mockContext.collections.FulfillmentRestrictions.toArray.mockReturnValue(Promise.resolve(mockMethodRestrictions)); + + const allowedMethods = await filterShippingMethods(mockContext, mockShippingMethod, mockHydratedOrder); + + expect(allowedMethods).toEqual([]); +}); + +test("deny method - region on deny list, no item restrictions", async () => { + // Shipping Location: US, CA, 90405 + + const mockMethodRestrictions = [ + { + _id: "allow001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "allow", + destination: { + country: [ + "US" + ] + } + }, + { + _id: "deny001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "deny", + destination: { + region: [ + "CA" + ] + } + } + ]; + + mockContext.collections.FulfillmentRestrictions.toArray.mockReturnValue(Promise.resolve(mockMethodRestrictions)); + + const allowedMethods = await filterShippingMethods(mockContext, mockShippingMethod, mockHydratedOrder); + + expect(allowedMethods).toEqual([]); +}); + +test("deny method - postal on deny list, no item restrictions", async () => { + // Shipping Location: US, CA, 90405 + + const mockMethodRestrictions = [ + { + _id: "allow001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "allow", + destination: { + country: [ + "US" + ] + } + }, + { + _id: "deny001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "deny", + destination: { + postal: [ + "90405" + ] + } + } + ]; + + mockContext.collections.FulfillmentRestrictions.toArray.mockReturnValue(Promise.resolve(mockMethodRestrictions)); + + const allowedMethods = await filterShippingMethods(mockContext, mockShippingMethod, mockHydratedOrder); + + expect(allowedMethods).toEqual([]); +}); + +test("deny method - region on one deny list, but also is not on other deny lists", async () => { + // Shipping Location: US, CA, 90405 + + const mockMethodRestrictions = [ + { + _id: "allow001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "allow", + destination: { + country: [ + "US" + ] + } + }, + { + _id: "deny001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "deny", + destination: { + region: [ + "CA" + ] + } + }, + { + _id: "deny002", + methodIds: [ + "nUjYh7hYtbUh0Ojht7" + ], + type: "deny", + destination: { + region: [ + "NY" + ] + } + } + ]; + + mockContext.collections.FulfillmentRestrictions.toArray.mockReturnValue(Promise.resolve(mockMethodRestrictions)); + + const allowedMethods = await filterShippingMethods(mockContext, mockShippingMethod, mockHydratedOrder); + + expect(allowedMethods).toEqual([]); +}); + +/* + * Test with item / attribute restrictions that deny method + */ +test("deny method - vendor on deny list", async () => { + const mockMethodRestrictions = [ + { + _id: "allow001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "allow", + destination: { + country: [ + "US" + ] + } + }, + { + _id: "deny001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "deny", + attributes: [ + { + property: "vendor", + value: "Restricted Vendor", + propertyType: "string", + operator: "eq" + } + ] + } + ]; + + mockContext.collections.FulfillmentRestrictions.toArray.mockReturnValue(Promise.resolve(mockMethodRestrictions)); + + const allowedMethods = await filterShippingMethods(mockContext, mockShippingMethod, mockHydratedOrder); + + expect(allowedMethods).toEqual([]); +}); + +test("deny method - item weight is too high", async () => { + const mockMethodRestrictions = [ + { + _id: "allow001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "allow", + destination: { + country: [ + "US" + ] + } + }, + { + _id: "deny001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "deny", + attributes: [ + { + property: "weight", + value: 40, + propertyType: "int", + operator: "gt" + } + ] + } + ]; + + mockContext.collections.FulfillmentRestrictions.toArray.mockReturnValue(Promise.resolve(mockMethodRestrictions)); + + const allowedMethods = await filterShippingMethods(mockContext, mockShippingMethod, mockHydratedOrder); + + expect(allowedMethods).toEqual([]); +}); + +test("deny method - item value is less than $100", async () => { + const mockMethodRestrictions = [ + { + _id: "allow001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "allow", + destination: { + country: [ + "US" + ] + } + }, + { + _id: "deny001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "deny", + attributes: [ + { + property: "price", + value: 100, + propertyType: "int", + operator: "lt" + } + ] + } + ]; + + mockContext.collections.FulfillmentRestrictions.toArray.mockReturnValue(Promise.resolve(mockMethodRestrictions)); + + const allowedMethods = await filterShippingMethods(mockContext, mockShippingMethod, mockHydratedOrder); + + expect(allowedMethods).toEqual([]); +}); + +test("deny method - multiple attributes - item value is less than $100 AND item weight is too high", async () => { + const mockMethodRestrictions = [ + { + _id: "allow001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "allow", + destination: { + country: [ + "US" + ] + } + }, + { + _id: "deny001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "deny", + attributes: [ + { + property: "price", + value: 100, + propertyType: "int", + operator: "lt" + }, + { + property: "weight", + value: 40, + propertyType: "int", + operator: "gt" + } + ] + } + ]; + + mockContext.collections.FulfillmentRestrictions.toArray.mockReturnValue(Promise.resolve(mockMethodRestrictions)); + + const allowedMethods = await filterShippingMethods(mockContext, mockShippingMethod, mockHydratedOrder); + + expect(allowedMethods).toEqual([]); +}); diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/isDestinationRestricted.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/isDestinationRestricted.js new file mode 100644 index 00000000000..cc08c5084f9 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/isDestinationRestricted.js @@ -0,0 +1,42 @@ +/** + * @summary Returns true if the given shipping address is restricted by destination rules + * @param {Object} destination Destination restrictions + * @param {Object} [shippingAddress] Shipping address, if known + * @return {Boolean} True if restricted + */ +export default function isDestinationRestricted(destination, shippingAddress) { + // If there is no shipping address, we can't restrict by destination + if (!shippingAddress) return false; + + const { country: restrictionCountry, postal: restrictionPostal, region: restrictionRegion } = destination; + + // Start checking at the micro-level, and move more macro as we go on + if (restrictionPostal && restrictionPostal.includes(shippingAddress.postal)) { + return true; + } + + // Check for an allow list of regions + if (restrictionRegion && restrictionRegion.includes(shippingAddress.region)) { + return true; + } + + // Check for an allow list of countries + if (restrictionCountry && restrictionCountry.includes(shippingAddress.country)) { + return true; + } + + /** + * This adds support for address regex match on address1 and address2 + * It is required to implement basic match against PO Box addresses + */ + if (destination.addressPattern) { + const { addressPatternShouldMatch = true } = destination; + const fullAddress = [shippingAddress.address1, shippingAddress.address2].join(" "); + const addressMatchesPattern = RegExp(destination.addressPattern, "ig").test(fullAddress); + if ((addressMatchesPattern && addressPatternShouldMatch) || (!addressMatchesPattern && !addressPatternShouldMatch)) { + return true; + } + } + + return false; +} diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/isShippingRestricted.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/isShippingRestricted.js new file mode 100644 index 00000000000..b22bf3a4e28 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/isShippingRestricted.js @@ -0,0 +1,53 @@ +import operators from "@reactioncommerce/api-utils/operators.js"; +import propertyTypes from "@reactioncommerce/api-utils/propertyTypes.js"; +import isDestinationRestricted from "./isDestinationRestricted.js"; + +/** + * @summary Filter shipping methods based on global restrictions + * @param {Object} context - an object containing the per-request state + * @param {Object} hydratedOrder - computed hydratedOrder for current order + * @returns {Object|null} available shipping methods after filtering + */ +export default async function isShippingRestricted(context, hydratedOrder) { + const { items, shippingAddress } = hydratedOrder; + const { FulfillmentRestrictions } = context.collections; + const universalRestrictions = await FulfillmentRestrictions.find({ methodIds: null, type: "deny" }).toArray(); + + // If there are no universal restrictions, move on to method specific checks + if (universalRestrictions.length === 0) { + return false; + } + + const doItemsContainUniversalRestrictions = items.some((item) => { // eslint-disable-line + // If any item matches a restriction, restrict method + return universalRestrictions.some((restriction) => { + const { attributes, destination } = restriction; + + // If attributes exist, check for attribute matches + attribute && destination matches + if (attributes && Array.isArray(attributes) && attributes.length) { + return attributes.some((attribute) => { + const attributeFound = operators[attribute.operator](item[attribute.property], propertyTypes[attribute.propertyType](attribute.value)); + + if (attributeFound) { + // If there is no destination restriction, destination restriction is global + return !destination || isDestinationRestricted(destination, shippingAddress); + } + + // If shipping location does not match restricted location && attribute, method is not restricted + return false; + }); + } + + if (destination) { + // There are no attribute restrictions, only check destination restrictions + return isDestinationRestricted(destination, shippingAddress); + } + + return false; + }); + }); + + // If restrictions are found, return true + // If they aren't found, return false + return doItemsContainUniversalRestrictions; +} diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/locationAllowCheck.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/locationAllowCheck.js new file mode 100644 index 00000000000..b06ced84282 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/locationAllowCheck.js @@ -0,0 +1,27 @@ +import isDestinationRestricted from "./isDestinationRestricted.js"; + +/** + * @summary Filter shipping methods based on per method allow location restrictions + * @param {Object} methodRestrictions - method restrictions from FlatRateFulfillmentRestrcitionsCollection + * @param {Object} method - current method to check restrictions against + * @param {Object} hydratedOrder - hydrated order for current order + * @returns {Bool} true / false as to whether method is still valid after this check + */ +export async function locationAllowCheck(methodRestrictions, method, hydratedOrder) { + const { shippingAddress } = hydratedOrder; + // Get method specific destination allow restrictions + const allowRestrictions = methodRestrictions.filter((restriction) => restriction.type === "allow"); + + // If there are no location deny restrictions, this method is valid at this point + if (allowRestrictions.length === 0) return true; + + // Loop over each allow restriction and determine if this method is valid + // If any levels of destination match, this method is valid at this point + const isAllowed = allowRestrictions.some((methodRestriction) => { + const { destination } = methodRestriction; + + return !destination || isDestinationRestricted(destination, shippingAddress); + }); + + return isAllowed; +} diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/locationDenyCheck.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/locationDenyCheck.js new file mode 100644 index 00000000000..2ad2c55bd4f --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/locationDenyCheck.js @@ -0,0 +1,27 @@ +import isDestinationRestricted from "./isDestinationRestricted.js"; + +/** + * @summary Filter shipping methods based on per method deny location restrictions + * @param {Object} methodRestrictions - method restrictions from FlatRateFulfillmentRestrcitionsCollection + * @param {Object} method - current method to check restrictions against + * @param {Object} hydratedOrder - hydrated order for current order + * @returns {Bool} true / false as to whether method is still valid after this check + */ +export async function locationDenyCheck(methodRestrictions, method, hydratedOrder) { + const { shippingAddress } = hydratedOrder; + // Get method specific allow restrictions + const denyRestrictions = methodRestrictions.filter((restriction) => restriction.type === "deny"); + + // If there are no destination deny restrictions, this method is valid at this point + if (denyRestrictions.length === 0) return true; + + // Loop over each deny restriction and determine if this method is valid + // If any levels of destination match, this method is invalid at this point + const isAllowed = denyRestrictions.every((methodRestriction) => { + const { destination } = methodRestriction; + + return !destination || !isDestinationRestricted(destination, shippingAddress); + }); + + return isAllowed; +} diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/methodSchema.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/methodSchema.js new file mode 100644 index 00000000000..4b80cb36723 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/methodSchema.js @@ -0,0 +1,25 @@ +import SimpleSchema from "simpl-schema"; + +const methodSchema = new SimpleSchema({ + "cost": { + type: Number, + optional: true + }, + "fulfillmentTypes": { + type: Array, + minCount: 1 + }, + "fulfillmentTypes.$": String, + "group": String, + "handling": Number, + "isEnabled": Boolean, + "label": String, + "name": String, + "fulfillmentMethod": { + type: String, + optional: true + }, + "rate": Number +}); + +export default methodSchema; diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/restrictionSchema.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/restrictionSchema.js new file mode 100644 index 00000000000..01c18ae28b5 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/restrictionSchema.js @@ -0,0 +1,63 @@ +import SimpleSchema from "simpl-schema"; + +/** + * @name Attributes + * @memberof Schemas + * @type {SimpleSchema} + * @property {String} property required + * @property {String} value required + * @property {String} propertyType required + * @property {String} operator required + */ +export const Attributes = new SimpleSchema({ + property: String, + value: String, + propertyType: String, + operator: String +}); + +/** + * @name Destination + * @memberof Schemas + * @type {SimpleSchema} + * @property {String} country optional + * @property {String} region optional + * @property {String} postal optional + */ +export const Destination = new SimpleSchema({ + "country": { + type: Array, + optional: true + }, + "country.$": String, + "region": { + type: Array, + optional: true + }, + "region.$": String, + "postal": { + type: Array, + optional: true + }, + "postal.$": String +}); + +const restrictionSchema = new SimpleSchema({ + "methodIds": { + type: Array, + optional: true + }, + "methodIds.$": String, + "type": String, + "attributes": { + type: Array, + optional: true + }, + "attributes.$": Attributes, + "destination": { + type: Destination, + optional: true + } +}); + +export default restrictionSchema; diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/validateOrderMethodsflatrate.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/validateOrderMethodsflatrate.js new file mode 100644 index 00000000000..54c9543954e --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/validateOrderMethodsflatrate.js @@ -0,0 +1,23 @@ +/** + * @summary Sample dummy function validating the data requirements for this metod + * @param {Object} context - Context + * @param {Object} commonOrder - Current order which provide available details to perform validation + * @param {Object[]} validationResults - Existing validation Results + * @returns {Object[]} validationResults - with the validation details populated + */ +export default function validateOrderMethodsflatrate(context, commonOrder, validationResults = []) { + // Add the code here to verify the data requirements of this particular plugin + // Sample template for returning any identified errors is as below + // If no errors, return the "validationResults" array as it is. + + const validationResult = { + errorName: "invalid", + errorType: "ReactionError", + errorField: "Flatrate shipping - some field", + fieldValue: "field-value", + errorMessage: "Customer address not available to decide shipping address" + }; + validationResults.push(validationResult); + + return validationResults; +} diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/xforms/id.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/xforms/id.js new file mode 100644 index 00000000000..64551f29b67 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/xforms/id.js @@ -0,0 +1,16 @@ +import decodeOpaqueIdForNamespace from "@reactioncommerce/api-utils/decodeOpaqueIdForNamespace.js"; +import encodeOpaqueId from "@reactioncommerce/api-utils/encodeOpaqueId.js"; + +const namespaces = { + FulfillmentMethod: "reaction/fulfillmentMethod", + FulfillmentRestriction: "reaction/flatRateFulfillmentRestriction", + Shop: "reaction/shop" +}; + +export const encodeFulfillmentMethodOpaqueId = encodeOpaqueId(namespaces.FulfillmentMethod); +export const encodeFulfillmentRestrictionOpaqueId = encodeOpaqueId(namespaces.FulfillmentRestriction); +export const encodeShopOpaqueId = encodeOpaqueId(namespaces.Shop); + +export const decodeFulfillmentMethodOpaqueId = decodeOpaqueIdForNamespace(namespaces.FulfillmentMethod); +export const decodeFulfillmentRestrictionOpaqueId = decodeOpaqueIdForNamespace(namespaces.FulfillmentRestriction); +export const decodeShopOpaqueId = decodeOpaqueIdForNamespace(namespaces.Shop); diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/.gitignore b/packages/api-plugin-fulfillment-method-shipping-ups/.gitignore new file mode 100644 index 00000000000..ad46b30886f --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-ups/.gitignore @@ -0,0 +1,61 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# next.js build output +.next diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/LICENSE b/packages/api-plugin-fulfillment-method-shipping-ups/LICENSE new file mode 100644 index 00000000000..261eeb9e9f8 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-ups/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/README.md b/packages/api-plugin-fulfillment-method-shipping-ups/README.md new file mode 100644 index 00000000000..3ece899287f --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-ups/README.md @@ -0,0 +1,70 @@ +# api-plugin-fulfillment-method-shipping-ups + +[![npm (scoped)](https://img.shields.io/npm/v/@reactioncommerce/api-plugin-fulfillment-method-shipping-ups.svg)](https://www.npmjs.com/package/@reactioncommerce/api-plugin-fulfillment-method-shipping-ups) +[![CircleCI](https://circleci.com/gh/reactioncommerce/api-plugin-fulfillment-method-shipping-ups.svg?style=svg)](https://circleci.com/gh/reactioncommerce/api-plugin-fulfillment-method-shipping-ups) + + +## Summary + +This plugin Implements Shipping-UPS as a fulfillment method under the type shipping. + +## Included in this fulfillment-method-shipping-ups plugin + +### `src/` + +The `src` folder contains all the plugin files. + +### `.gitignore` + +A basic `gitignore` file + +### `babel.config.cjs` + +If your plugin includes linting and tests, this file is required to allow esmodules to run correctly. + +### `jest.config.cjs` + +If your plugin includes tests, this file is required to allow esmodules to run correctly. You'll need to update the `transformIgnorePatterns` and `moduleNameMapper` sections to include any esmodule `npm` packages used in your plugin. + +### `License.md` + +If your plugin uses `Apache 2` licensing, you can leave this file as-is. If another type of licensing is used, you need to update this file, and the README, accordingly. + +### `package.json` + +The provided `package.json` is set up to install all needed packages and config for linting, testing, and semantic-release. You'll need to update the `name`, `description`, and add any new dependencies your plugin files use. + +### `index.js` + +The entrypoint file for your npm package, will most likely just export your plugin registration from the `src` folder. + +## Developer Certificate of Origin +We use the [Developer Certificate of Origin (DCO)](https://developercertificate.org/) in lieu of a Contributor License Agreement for all contributions to Reaction Commerce open source projects. We request that contributors agree to the terms of the DCO and indicate that agreement by signing all commits made to Reaction Commerce projects by adding a line with your name and email address to every Git commit message contributed: +``` +Signed-off-by: Jane Doe +``` + +You can sign your commit automatically with Git by using `git commit -s` if you have your `user.name` and `user.email` set as part of your Git configuration. + +We ask that you use your real name (please no anonymous contributions or pseudonyms). By signing your commit you are certifying that you have the right have the right to submit it under the open source license used by that particular Reaction Commerce project. You must use your real name (no pseudonyms or anonymous contributions are allowed.) + +We use the [Probot DCO GitHub app](https://github.com/apps/dco) to check for DCO signoffs of every commit. + +If you forget to sign your commits, the DCO bot will remind you and give you detailed instructions for how to amend your commits to add a signature. + +## License + + Copyright 2020 Reaction Commerce + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/babel.config.cjs b/packages/api-plugin-fulfillment-method-shipping-ups/babel.config.cjs new file mode 100644 index 00000000000..5fa924c0809 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-ups/babel.config.cjs @@ -0,0 +1 @@ +module.exports = require("@reactioncommerce/api-utils/lib/configs/babel.config.cjs"); diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/index.js b/packages/api-plugin-fulfillment-method-shipping-ups/index.js new file mode 100644 index 00000000000..d7ea8b28c59 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-ups/index.js @@ -0,0 +1,3 @@ +import register from "./src/index.js"; + +export default register; diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/jest.config.cjs b/packages/api-plugin-fulfillment-method-shipping-ups/jest.config.cjs new file mode 100644 index 00000000000..2bdefefceb9 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-ups/jest.config.cjs @@ -0,0 +1 @@ +module.exports = require("@reactioncommerce/api-utils/lib/configs/jest.config.cjs"); diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/package.json b/packages/api-plugin-fulfillment-method-shipping-ups/package.json new file mode 100644 index 00000000000..2c969350ce6 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-ups/package.json @@ -0,0 +1,43 @@ +{ + "name": "@reactioncommerce/api-plugin-fulfillment-method-shipping-ups", + "description": "Implements Shipping-UPS as a fulfillment method under the type shipping", + "label": "Fulfillment Method Shipping UPS", + "version": "1.0.0", + "main": "index.js", + "type": "module", + "engines": { + "node": ">=14.18.1", + "npm": ">=7" + }, + "homepage": "https://github.com/reactioncommerce/reaction", + "url": "https://github.com/reactioncommerce/reaction", + "email": "hello-open-commerce@mailchimp.com", + "repository": { + "type": "git", + "url": "git+https://github.com/reactioncommerce/reaction.git", + "directory": "packages/api-plugin-fulfillment-method-shipping-ups" + }, + "author": { + "name": "Mailchimp Open Commerce", + "email": "hello-open-commerce@mailchimp.com", + "url": "https://mailchimp.com/developer/open-commerce/" + }, + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/reactioncommerce/reaction/issues" + }, + "sideEffects": false, + "dependencies": { + "simpl-schema": "^1.12.2", + "@reactioncommerce/reaction-error": "^1.0.1", + "@reactioncommerce/api-utils": "^1.16.9" + }, + "devDependencies": {}, + "scripts": { + "lint": "npm run lint:eslint", + "lint:eslint": "eslint .", + "test": "jest", + "test:watch": "jest --watch", + "test:file": "jest --no-cache --watch --coverage=false" + } +} diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/getFulfillmentMethodsWithQuotesShippingUPS.js b/packages/api-plugin-fulfillment-method-shipping-ups/src/getFulfillmentMethodsWithQuotesShippingUPS.js new file mode 100644 index 00000000000..da77724a709 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-ups/src/getFulfillmentMethodsWithQuotesShippingUPS.js @@ -0,0 +1,72 @@ +import calculateUPSRate from "./util/calculateUPSRate.js"; + +const packageName = "fulfillment-method-shipping-ups"; +const fulfillmentTypeName = "shipping"; +const fulfillmentMethodName = "ups"; + +/** + * @summary Returns a list of fulfillment method quotes based on the items in a fulfillment group. + * @param {Object} context - Context + * @param {Object} commonOrder - details about the purchase a user wants to make. + * @param {Array} [previousQueryResults] - an array of shipping rates and + * info about failed calls to the APIs of some shipping methods providers + * e.g Shippo. + * @returns {Array} - an array that contains two arrays: the first array will + * be an updated list of shipping rates, and the second will contain info for + * retrying this specific package if any errors occurred while retrieving the + * shipping rates. + * @private + */ +export default async function getFulfillmentMethodsWithQuotesShippingUPS(context, commonOrder, previousQueryResults = []) { + const { collections } = context; + const { Fulfillment } = collections; + const [rates = [], retrialTargets = []] = previousQueryResults; + const currentMethodInfo = { packageName }; + + if (retrialTargets.length > 0) { + const isNotAmongFailedRequests = retrialTargets.every((target) => target.packageName !== packageName); + if (isNotAmongFailedRequests) { + return previousQueryResults; + } + } + + const shippingRateDocs = await Fulfillment.find({ + "shopId": commonOrder.shopId, + "fulfillmentType": fulfillmentTypeName, + "provider.enabled": true + }).toArray(); + if (!shippingRateDocs || !shippingRateDocs.length) { + return [rates, retrialTargets]; + } + const initialNumOfRates = rates.length; + + const awaitedShippingRateDocs = shippingRateDocs.map(async (doc) => { + const carrier = doc.provider.label; + const currentPluginMethods = doc.methods.filter((method) => ((method.fulfillmentMethod === (fulfillmentMethodName)) && (method.enabled))); + for (const method of currentPluginMethods) { + const updatedMethod = calculateUPSRate(method, commonOrder); + rates.push({ + carrier, + handlingPrice: updatedMethod.handling, + method: updatedMethod, + rate: updatedMethod.rate, + shippingPrice: updatedMethod.rate + updatedMethod.handling, + shopId: doc.shopId + }); + } + }); + await Promise.all(awaitedShippingRateDocs); + + if (rates.length === initialNumOfRates) { + const errorDetails = { + requestStatus: "error", + shippingProvider: packageName, + message: "UPS shipping did not return any shipping methods." + }; + rates.push(errorDetails); + retrialTargets.push(currentMethodInfo); + return [rates, retrialTargets]; + } + + return [rates, retrialTargets]; +} diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/index.js b/packages/api-plugin-fulfillment-method-shipping-ups/src/index.js new file mode 100644 index 00000000000..044a88d135f --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-ups/src/index.js @@ -0,0 +1,36 @@ +import { createRequire } from "module"; +import { MethodUPSData } from "./simpleSchemas.js"; +import preStartup from "./preStartup.js"; +import startup from "./startup.js"; +import schemas from "./schemas/index.js"; +import getFulfillmentMethodsWithQuotesShippingUPS from "./getFulfillmentMethodsWithQuotesShippingUPS.js"; +import validateOrderMethodsups from "./util/validateOrderMethodsups.js"; + +const require = createRequire(import.meta.url); +const pkg = require("../package.json"); + +/** + * @summary Import and call this function to add this plugin to your API. + * @param {Object} app The ReactionAPI instance + * @returns {undefined} + */ +export default async function register(app) { + await app.registerPlugin({ + label: "Fulfillment Method Shipping UPS", + name: "fulfillment-method-shipping-ups", + version: pkg.version, + graphQL: { + schemas + }, + simpleSchemas: { + MethodUPSData + }, + functionsByType: { + preStartup: [preStartup], + startup: [startup], + validateOrderMethods: [validateOrderMethodsups], + getFulfillmentMethodsWithQuotes: [getFulfillmentMethodsWithQuotesShippingUPS], + getFulfillmentMethodsWithQuotesShipping: [getFulfillmentMethodsWithQuotesShippingUPS] + } + }); +} diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/preStartup.js b/packages/api-plugin-fulfillment-method-shipping-ups/src/preStartup.js new file mode 100644 index 00000000000..109faea35d7 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-ups/src/preStartup.js @@ -0,0 +1,28 @@ +import { MethodUPSData } from "./simpleSchemas.js"; + +/** + * @summary Called on preStartup to extend schemas + * @param {Object} context Startup context + * @returns {undefined} + */ +export default async function fulfillmentMethodShippingUPSPreStartup(context) { + const { simpleSchemas: { ShippingMethod, SelectedFulfillmentOption } } = context; + + ShippingMethod.extend({ + methodAdditionalData: { + type: ShippingMethod.getDefinition( + "methodAdditionalData", + ["type"] + ).type[0].type.extend(MethodUPSData) + } + }); + + SelectedFulfillmentOption.extend({ + methodAdditionalData: { + type: SelectedFulfillmentOption.getDefinition( + "methodAdditionalData", + ["type"] + ).type[0].type.extend(MethodUPSData) + } + }); +} diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/schemas/index.js b/packages/api-plugin-fulfillment-method-shipping-ups/src/schemas/index.js new file mode 100644 index 00000000000..30096f92e54 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-ups/src/schemas/index.js @@ -0,0 +1,5 @@ +import importAsString from "@reactioncommerce/api-utils/importAsString.js"; + +const schema = importAsString("./schema.graphql"); + +export default [schema]; diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/schemas/schema.graphql b/packages/api-plugin-fulfillment-method-shipping-ups/src/schemas/schema.graphql new file mode 100644 index 00000000000..bab8feb5c93 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-ups/src/schemas/schema.graphql @@ -0,0 +1,7 @@ +"Additional data from Shipping UPS" +type upsData { + gqlType: String + upsData: String +} + +extend union AdditionalData = upsData diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/simpleSchemas.js b/packages/api-plugin-fulfillment-method-shipping-ups/src/simpleSchemas.js new file mode 100644 index 00000000000..022235d6ca0 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-ups/src/simpleSchemas.js @@ -0,0 +1,17 @@ +import SimpleSchema from "simpl-schema"; + +/** + * @name MethodUPSData + * @memberof Schemas + * @type {SimpleSchema} + * @summary Defines UPS additional data + * @property {String} gqlType Defines the method type + * @property {String} upsData UPS Data fields + */ +export const MethodUPSData = new SimpleSchema({ + gqlType: String, + upsData: { + type: String, + optional: true + } +}); diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/startup.js b/packages/api-plugin-fulfillment-method-shipping-ups/src/startup.js new file mode 100644 index 00000000000..5abe1766f3a --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-ups/src/startup.js @@ -0,0 +1,39 @@ +import ReactionError from "@reactioncommerce/reaction-error"; + +/** + * @summary Called on startup to create the root entry of this fulfillment type in Fulfillment collection + * @param {Object} context Startup context + * @param {Object} context.collections Map of MongoDB collections + * @returns {undefined} + */ +export default async function fulfillmentMethodShippingUPSStartup(context) { + const { collections } = context; + const { Fulfillment } = collections; + + context.appEvents.on("afterShopCreate", async (payload) => { + const { shop } = payload; + const shopId = shop._id; + const method = {}; + + // We do not have validatePermissions in context during this startup stage, hence commenting below + // await context.validatePermissions("reaction:legacy:fulfillmentTypes", "read", { shopId }); + + const shippingRecord = await Fulfillment.findOne({ fulfillmentType: "shipping", shopId }); + if (!shippingRecord) throw new ReactionError("not-configured", "Unable to create fulfillment method Shipping-UPS without defined type"); + + const fulfillmentTypeId = shippingRecord._id; + method.name = "ups"; + method.label = "Shipping via UPS"; + method.fulfillmentTypes = ["shipping"]; + method.group = "Ground"; + method.cost = 0; + method.handling = 0; + method.rate = 0; + method.enabled = true; + method.fulfillmentMethod = "ups"; + method.displayMessageMethod = "Placeholder for display message"; + + await context.mutations.createFulfillmentMethod(context.getInternalContext(), { shopId, fulfillmentTypeId, method }); + return { method }; + }); +} diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/util/calculateUPSRate.js b/packages/api-plugin-fulfillment-method-shipping-ups/src/util/calculateUPSRate.js new file mode 100644 index 00000000000..76bc9e808a5 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-ups/src/util/calculateUPSRate.js @@ -0,0 +1,33 @@ +/** + * @summary Sample dummy function to simulate custom logic to retrieve additional data of the selected method + * @param {Object} currentOrder - Current order which provide required details to perform rate calculation + * @returns {Object} methodData - additional data + */ +function getUPSData() { + // currentOrder details could be passed in here and used as input to obtain any external data + return { + gqlType: "upsData", + upsData: "This is additional STRING data from Shipping - UPS" + }; +} +/** + * @summary Sample dummy function to simulate custom logic to retrieve the rates of the selected method + * @param {Object} method - current method for which rates are to be retrieved + * @param {Object} currentOrder - Current order which provide required details to perform rate calculation + * @returns {Object} updatedMethod - with the rate details populated + */ +export default function calculateUPSRate(method, currentOrder) { + // Collect order specific details for calculating the rates + // const { items, shippingAddress } = currentOrder; + + // Make call to the external API of this Fulfillment method to collect the rates + // Or we could have custom logic implemented here hat returns dynamic rate + // Below we are just hardcoding with some dummy values + const updatedMethod = method; + updatedMethod.rate = 10; + updatedMethod.handling = 20; + updatedMethod.carrier = "UPS"; + updatedMethod.methodAdditionalData = getUPSData(currentOrder); + + return updatedMethod; +} diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/util/sampleTest.test.js b/packages/api-plugin-fulfillment-method-shipping-ups/src/util/sampleTest.test.js new file mode 100644 index 00000000000..d94db67445e --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-ups/src/util/sampleTest.test.js @@ -0,0 +1,3 @@ +test("sample test", async () => { + expect(1 + 1).toBe(2); +}); diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/util/validateOrderMethodsups.js b/packages/api-plugin-fulfillment-method-shipping-ups/src/util/validateOrderMethodsups.js new file mode 100644 index 00000000000..eb9a0286fa1 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-ups/src/util/validateOrderMethodsups.js @@ -0,0 +1,22 @@ +/** + * @summary Sample dummy function validating the data requirements for this metod + * @param {Object} context - Context object + * @param {Object} commonOrder - Current order which provide available details to perform validation + * @param {Object[]} validationResults - Validation results collected till now + * @returns {Object[]} validationResults - with the validation details populated + */ +export default function validateOrderMethodsups(context, commonOrder, validationResults = []) { + // const { items, shippingAddress } = commonOrder; + + const validationResult = { + errorName: "invalid", + errorType: "ReactionError", + errorField: "UPS - some field", + fieldValue: "field-value", + errorMessage: "Customer address not available to decide shipping address" + }; + + validationResults.push(validationResult); + + return validationResults; +} diff --git a/packages/api-plugin-fulfillment-type-shipping/.gitignore b/packages/api-plugin-fulfillment-type-shipping/.gitignore new file mode 100644 index 00000000000..ad46b30886f --- /dev/null +++ b/packages/api-plugin-fulfillment-type-shipping/.gitignore @@ -0,0 +1,61 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# next.js build output +.next diff --git a/packages/api-plugin-fulfillment-type-shipping/LICENSE b/packages/api-plugin-fulfillment-type-shipping/LICENSE new file mode 100644 index 00000000000..261eeb9e9f8 --- /dev/null +++ b/packages/api-plugin-fulfillment-type-shipping/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/api-plugin-fulfillment-type-shipping/README.md b/packages/api-plugin-fulfillment-type-shipping/README.md new file mode 100644 index 00000000000..863277b6826 --- /dev/null +++ b/packages/api-plugin-fulfillment-type-shipping/README.md @@ -0,0 +1,72 @@ +# api-plugin-fulfillment-type-shipping + +[![npm (scoped)](https://img.shields.io/npm/v/@reactioncommerce/api-plugin-fulfillment-type-shipping.svg)](https://www.npmjs.com/package/@reactioncommerce/api-plugin-fulfillment-type-shipping) +[![CircleCI](https://circleci.com/gh/reactioncommerce/api-plugin-fulfillment-type-shipping.svg?style=svg)](https://circleci.com/gh/reactioncommerce/api-plugin-fulfillment-type-shipping) + + +## Summary + +This plugin implements 'shipping' as one of the fulfillment type. Fulfillment methods under 'shipping' is implemented via plugins like api-plugin-fulfillment-method-shipping-flat-rate and api-plugin-fulfillment-method-shipping-ups. + +The `Developer Certificate of Origin` and `License` sections can stay as they are, assuming `Apache 2` license is used (our preferred license). All other sections of this README should be updated to reflect your plugin. + +## Included in this fulfillment-type-shipping plugin + +### `src/` + +The `src` folder is where you'll put all the plugin files. + +### `.gitignore` + +A basic `gitignore` file + +### `babel.config.cjs` + +If your plugin includes linting and tests, this file is required to allow esmodules to run correctly. + +### `jest.config.cjs` + +If your plugin includes tests, this file is required to allow esmodules to run correctly. You'll need to update the `transformIgnorePatterns` and `moduleNameMapper` sections to include any esmodule `npm` packages used in your plugin. + +### `License.md` + +If your plugin uses `Apache 2` licensing, you can leave this file as-is. If another type of licensing is used, you need to update this file, and the README, accordingly. + +### `package.json` + +The provided `package.json` is set up to install all needed packages and config for linting, testing, and semantic-release. You'll need to update the `name`, `description`, and add any new dependencies your plugin files use. + +### `index.js` + +The entrypoint file for your npm package, will most likely just export your plugin registration from the `src` folder. + +## Developer Certificate of Origin +We use the [Developer Certificate of Origin (DCO)](https://developercertificate.org/) in lieu of a Contributor License Agreement for all contributions to Reaction Commerce open source projects. We request that contributors agree to the terms of the DCO and indicate that agreement by signing all commits made to Reaction Commerce projects by adding a line with your name and email address to every Git commit message contributed: +``` +Signed-off-by: Jane Doe +``` + +You can sign your commit automatically with Git by using `git commit -s` if you have your `user.name` and `user.email` set as part of your Git configuration. + +We ask that you use your real name (please no anonymous contributions or pseudonyms). By signing your commit you are certifying that you have the right have the right to submit it under the open source license used by that particular Reaction Commerce project. You must use your real name (no pseudonyms or anonymous contributions are allowed.) + +We use the [Probot DCO GitHub app](https://github.com/apps/dco) to check for DCO signoffs of every commit. + +If you forget to sign your commits, the DCO bot will remind you and give you detailed instructions for how to amend your commits to add a signature. + +## License + + Copyright 2020 Reaction Commerce + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/packages/api-plugin-fulfillment-type-shipping/babel.config.cjs b/packages/api-plugin-fulfillment-type-shipping/babel.config.cjs new file mode 100644 index 00000000000..5fa924c0809 --- /dev/null +++ b/packages/api-plugin-fulfillment-type-shipping/babel.config.cjs @@ -0,0 +1 @@ +module.exports = require("@reactioncommerce/api-utils/lib/configs/babel.config.cjs"); diff --git a/packages/api-plugin-fulfillment-type-shipping/index.js b/packages/api-plugin-fulfillment-type-shipping/index.js new file mode 100644 index 00000000000..d7ea8b28c59 --- /dev/null +++ b/packages/api-plugin-fulfillment-type-shipping/index.js @@ -0,0 +1,3 @@ +import register from "./src/index.js"; + +export default register; diff --git a/packages/api-plugin-fulfillment-type-shipping/jest.config.cjs b/packages/api-plugin-fulfillment-type-shipping/jest.config.cjs new file mode 100644 index 00000000000..2bdefefceb9 --- /dev/null +++ b/packages/api-plugin-fulfillment-type-shipping/jest.config.cjs @@ -0,0 +1 @@ +module.exports = require("@reactioncommerce/api-utils/lib/configs/jest.config.cjs"); diff --git a/packages/api-plugin-fulfillment-type-shipping/package.json b/packages/api-plugin-fulfillment-type-shipping/package.json new file mode 100644 index 00000000000..3a5f7db0fe3 --- /dev/null +++ b/packages/api-plugin-fulfillment-type-shipping/package.json @@ -0,0 +1,46 @@ +{ + "name": "@reactioncommerce/api-plugin-fulfillment-type-shipping", + "description": "Plugin which implements shipping as one of the Fulfillment type", + "label": "Fulfillment type Shipping Plugin", + "version": "1.0.0", + "main": "index.js", + "type": "module", + "engines": { + "node": ">=14.18.1", + "npm": ">=7" + }, + "homepage": "https://github.com/reactioncommerce/reaction", + "url": "https://github.com/reactioncommerce/reaction", + "email": "hello-open-commerce@mailchimp.com", + "repository": { + "type": "git", + "url": "git+https://github.com/reactioncommerce/reaction.git", + "directory": "packages/api-plugin-fulfillment-type-shipping" + }, + "author": { + "name": "Mailchimp Open Commerce", + "email": "hello-open-commerce@mailchimp.com", + "url": "https://mailchimp.com/developer/open-commerce/" + }, + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/reactioncommerce/reaction/issues" + }, + "sideEffects": false, + "dependencies": { + "@reactioncommerce/api-utils": "^1.16.9", + "@reactioncommerce/logger": "^1.1.3", + "@reactioncommerce/reaction-error": "^1.0.1", + "@reactioncommerce/random": "~1.0.2", + "lodash": "^4.17.21", + "simpl-schema": "^1.12.2" + }, + "devDependencies": {}, + "scripts": { + "lint": "npm run lint:eslint", + "lint:eslint": "eslint .", + "test": "jest", + "test:watch": "jest --watch", + "test:file": "jest --no-cache --watch --coverage=false" + } +} diff --git a/packages/api-plugin-fulfillment-type-shipping/src/index.js b/packages/api-plugin-fulfillment-type-shipping/src/index.js new file mode 100644 index 00000000000..04ede1902a1 --- /dev/null +++ b/packages/api-plugin-fulfillment-type-shipping/src/index.js @@ -0,0 +1,33 @@ +import { createRequire } from "module"; +import schemas from "./schemas/index.js"; +import startup from "./startup.js"; + +const require = createRequire(import.meta.url); +const pkg = require("../package.json"); + +const { name, version } = pkg; +export const logCtx = { + name, + version, + file: "src/index.js" +}; + +/** + * @summary Import and call this function to add this plugin to your API. + * @param {Object} app The ReactionAPI instance + * @returns {undefined} + */ +export default async function register(app) { + await app.registerPlugin({ + label: "Fulfillment Type Shipping", + name: "fulfillment-type-shipping", + version: pkg.version, + graphQL: { + schemas + }, + functionsByType: { + startup: [startup] + }, + registeredFulfillmentTypes: ["shipping"] + }); +} diff --git a/packages/api-plugin-fulfillment-type-shipping/src/sampleTest.test.js b/packages/api-plugin-fulfillment-type-shipping/src/sampleTest.test.js new file mode 100644 index 00000000000..d94db67445e --- /dev/null +++ b/packages/api-plugin-fulfillment-type-shipping/src/sampleTest.test.js @@ -0,0 +1,3 @@ +test("sample test", async () => { + expect(1 + 1).toBe(2); +}); diff --git a/packages/api-plugin-fulfillment-type-shipping/src/schemas/index.js b/packages/api-plugin-fulfillment-type-shipping/src/schemas/index.js new file mode 100644 index 00000000000..30096f92e54 --- /dev/null +++ b/packages/api-plugin-fulfillment-type-shipping/src/schemas/index.js @@ -0,0 +1,5 @@ +import importAsString from "@reactioncommerce/api-utils/importAsString.js"; + +const schema = importAsString("./schema.graphql"); + +export default [schema]; diff --git a/packages/api-plugin-fulfillment-type-shipping/src/schemas/schema.graphql b/packages/api-plugin-fulfillment-type-shipping/src/schemas/schema.graphql new file mode 100644 index 00000000000..569bf06309c --- /dev/null +++ b/packages/api-plugin-fulfillment-type-shipping/src/schemas/schema.graphql @@ -0,0 +1,29 @@ +"A shipping parcel" +type ShippingParcel { + "Containers" + containers: String + + "Distance unit" + distanceUnit: DistanceUnit + + "Height" + height: Float + + "Length" + length: Float + + "Mass unit" + massUnit: MassUnit + + "Weight" + weight: Float + + "Width" + width: Float +} + +"Allowed fulfillment types" +enum FulfillmentType { + "An order will be fulfilled by the seller shipping it to the customer" + shipping +} diff --git a/packages/api-plugin-fulfillment-type-shipping/src/startup.js b/packages/api-plugin-fulfillment-type-shipping/src/startup.js new file mode 100644 index 00000000000..23a73bfaf2a --- /dev/null +++ b/packages/api-plugin-fulfillment-type-shipping/src/startup.js @@ -0,0 +1,37 @@ +import Random from "@reactioncommerce/random"; + +/** + * @summary Called on startup to create the root entry of this fulfillment type in Fulfillment collection + * @param {Object} context Startup context + * @param {Object} context.collections Map of MongoDB collections + * @returns {undefined} + */ +export default async function fulfillmentTypeShippingStartup(context) { + const { collections } = context; + const { Fulfillment } = collections; + + context.appEvents.on("afterShopCreate", async (payload) => { + const { shop } = payload; + const shopId = shop._id; + + // We do not have validatePermissions in context during this startup stage, hence commenting below + // await context.validatePermissions("reaction:legacy:fulfillmentTypes", "read", { shopId }); + + const shippingRecord = await Fulfillment.findOne({ fulfillmentType: "shipping", shopId }); + if (!shippingRecord) { + const groupInfo = { + _id: Random.id(), + name: "Shipping Provider", + shopId, + provider: { + enabled: true, + label: "Shipping", + name: "shipping" + }, + fulfillmentType: "shipping" + }; + + await context.mutations.createFulfillmentType(context.getInternalContext(), groupInfo); + } + }); +} From d5252f5038eed5124fb1321f0a6a1efe691cfe94 Mon Sep 17 00:00:00 2001 From: Sujith Date: Thu, 20 Oct 2022 00:08:52 +0530 Subject: [PATCH 17/71] fix: updated test cases Signed-off-by: Sujith --- ...llmentMethodsWithQuotesPickupStore.test.js | 117 ++++++++++++++++++ .../src/startup.js | 26 +--- ...eckAndCreateFulfillmentMethod.test.js.snap | 3 + .../util/checkAndCreateFulfillmentMethod.js | 31 +++++ .../checkAndCreateFulfillmentMethod.test.js | 87 +++++++++++++ .../src/util/sampleTest.test.js | 3 - .../src/checkAndCreateFulfillmentType.js | 27 ++++ .../src/checkAndCreateFulfillmentType.test.js | 61 +++++++++ .../src/sampleTest.test.js | 3 - .../src/startup.js | 24 +--- 10 files changed, 336 insertions(+), 46 deletions(-) create mode 100644 packages/api-plugin-fulfillment-method-shipping-ups/src/getFulfillmentMethodsWithQuotesPickupStore.test.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-ups/src/util/__snapshots__/checkAndCreateFulfillmentMethod.test.js.snap create mode 100644 packages/api-plugin-fulfillment-method-shipping-ups/src/util/checkAndCreateFulfillmentMethod.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-ups/src/util/checkAndCreateFulfillmentMethod.test.js delete mode 100644 packages/api-plugin-fulfillment-method-shipping-ups/src/util/sampleTest.test.js create mode 100644 packages/api-plugin-fulfillment-type-shipping/src/checkAndCreateFulfillmentType.js create mode 100644 packages/api-plugin-fulfillment-type-shipping/src/checkAndCreateFulfillmentType.test.js delete mode 100644 packages/api-plugin-fulfillment-type-shipping/src/sampleTest.test.js diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/getFulfillmentMethodsWithQuotesPickupStore.test.js b/packages/api-plugin-fulfillment-method-shipping-ups/src/getFulfillmentMethodsWithQuotesPickupStore.test.js new file mode 100644 index 00000000000..86162252b17 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-ups/src/getFulfillmentMethodsWithQuotesPickupStore.test.js @@ -0,0 +1,117 @@ +import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; +import getFulfillmentMethodsWithQuotesShippingUPS from "./getFulfillmentMethodsWithQuotesShippingUPS.js"; + +test("should return previousResults if Shipping is not among FailedRequests", async () => { + const commonOrder = { + _id: "order123" + }; + const previousResults = [ + [], [ + { + packageName: "some-other-fulfillment-method" + } + ] + ]; + const result = await getFulfillmentMethodsWithQuotesShippingUPS(mockContext, commonOrder, previousResults); + expect(result).toEqual(previousResults); +}); + + +test("should return previousResults if not fulfillment records enabled", async () => { + const previousResults = [ + [ + { + carrier: "carrier123", + handlingPrice: 99, + rate: 99, + shippingPrice: 198, + shopId: "SHOP_ID" + } + ], [] + ]; + const commonOrder = { + _id: "order123" + }; + + mockContext.collections.Fulfillment = { + find: jest.fn(() => ({ toArray: () => [] })) + }; + const result = await getFulfillmentMethodsWithQuotesShippingUPS(mockContext, commonOrder, previousResults); + expect(result).toEqual(previousResults); +}); + + +test("should return rates witout error", async () => { + const previousResults = [ + [ + { + carrier: "Shipping", + handlingPrice: 10, + rate: 5, + shippingPrice: 15, + shopId: "SHOP_ID" + } + ], [] + ]; + const commonOrder = { + _id: "order123" + }; + const shippingDoc = { + _id: "fulfillment123", + name: "Default Shipping Provider", + shopId: "SHOP_ID", + provider: { + enabled: true, + label: "Shipping", + name: "shipping" + }, + fulfillmentType: "shipping", + methods: [{ + shopId: "SHOP_ID", + cost: 99, + handling: 99, + rate: 99, + fulfillmentTypes: ["shipping"], + group: "Ground", + enabled: true, + label: "UPS", + name: "ups", + fulfillmentMethod: "ups", + displayMessageMethod: "Sample display message" + }] + }; + const expectedNewRate = { + carrier: "Shipping", + handlingPrice: 20, + method: { + shopId: "SHOP_ID", + cost: 99, + handling: 20, + rate: 10, + fulfillmentTypes: [ + "shipping" + ], + group: "Ground", + enabled: true, + label: "UPS", + name: "ups", + fulfillmentMethod: "ups", + displayMessageMethod: "Sample display message", + carrier: "UPS", + methodAdditionalData: { + gqlType: "upsData", + upsData: "This is additional STRING data from Shipping - UPS" + } + }, + rate: 10, + shippingPrice: 30, + shopId: "SHOP_ID" + }; + const expectedResult = [[...previousResults[0], expectedNewRate], []]; + + mockContext.collections.Fulfillment = { + find: jest.fn(() => ({ toArray: () => [shippingDoc] })) + }; + const result = await getFulfillmentMethodsWithQuotesShippingUPS(mockContext, commonOrder, previousResults); + expect(result).toEqual(expectedResult); +}); diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/startup.js b/packages/api-plugin-fulfillment-method-shipping-ups/src/startup.js index 5abe1766f3a..bd625d8a227 100644 --- a/packages/api-plugin-fulfillment-method-shipping-ups/src/startup.js +++ b/packages/api-plugin-fulfillment-method-shipping-ups/src/startup.js @@ -1,4 +1,5 @@ import ReactionError from "@reactioncommerce/reaction-error"; +import checkAndCreateFulfillmentMethod from "./util/checkAndCreateFulfillmentMethod"; /** * @summary Called on startup to create the root entry of this fulfillment type in Fulfillment collection @@ -7,33 +8,16 @@ import ReactionError from "@reactioncommerce/reaction-error"; * @returns {undefined} */ export default async function fulfillmentMethodShippingUPSStartup(context) { - const { collections } = context; - const { Fulfillment } = collections; - context.appEvents.on("afterShopCreate", async (payload) => { const { shop } = payload; const shopId = shop._id; - const method = {}; // We do not have validatePermissions in context during this startup stage, hence commenting below // await context.validatePermissions("reaction:legacy:fulfillmentTypes", "read", { shopId }); - const shippingRecord = await Fulfillment.findOne({ fulfillmentType: "shipping", shopId }); - if (!shippingRecord) throw new ReactionError("not-configured", "Unable to create fulfillment method Shipping-UPS without defined type"); - - const fulfillmentTypeId = shippingRecord._id; - method.name = "ups"; - method.label = "Shipping via UPS"; - method.fulfillmentTypes = ["shipping"]; - method.group = "Ground"; - method.cost = 0; - method.handling = 0; - method.rate = 0; - method.enabled = true; - method.fulfillmentMethod = "ups"; - method.displayMessageMethod = "Placeholder for display message"; - - await context.mutations.createFulfillmentMethod(context.getInternalContext(), { shopId, fulfillmentTypeId, method }); - return { method }; + const insertedMethod = await checkAndCreateFulfillmentMethod(context, shopId); + if (!insertedMethod) { + throw new ReactionError("server-error", "Error in creating fulfillment method"); + } }); } diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/util/__snapshots__/checkAndCreateFulfillmentMethod.test.js.snap b/packages/api-plugin-fulfillment-method-shipping-ups/src/util/__snapshots__/checkAndCreateFulfillmentMethod.test.js.snap new file mode 100644 index 00000000000..9db0a1d7407 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-ups/src/util/__snapshots__/checkAndCreateFulfillmentMethod.test.js.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should throw error and NOT call createFulfillmentMethod mutation 1`] = `"Unable to create fulfillment method Shipping-UPS without defined type"`; diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/util/checkAndCreateFulfillmentMethod.js b/packages/api-plugin-fulfillment-method-shipping-ups/src/util/checkAndCreateFulfillmentMethod.js new file mode 100644 index 00000000000..83b95570ec0 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-ups/src/util/checkAndCreateFulfillmentMethod.js @@ -0,0 +1,31 @@ +import ReactionError from "@reactioncommerce/reaction-error"; +/** + * @summary Called on startup to create the root entry of this fulfillment type in Fulfillment collection + * @param {Object} context Startup context + * @param {String} shopId Shop ID + * @returns {Boolean} true if entry exist or insert success else false + */ +export default async function checkAndCreateFulfillmentMethod(context, shopId) { + const { collections } = context; + const { Fulfillment } = collections; + + const shippingRecord = await Fulfillment.findOne({ fulfillmentType: "shipping", shopId }); + if (!shippingRecord) throw new ReactionError("server-error", "Unable to create fulfillment method Shipping-UPS without defined type"); + + const fulfillmentTypeId = shippingRecord._id; + const method = { + name: "ups", + label: "Shipping via UPS", + fulfillmentTypes: ["shipping"], + group: "Ground", + cost: 0, + handling: 0, + rate: 0, + enabled: true, + fulfillmentMethod: "ups", + displayMessageMethod: "Placeholder for display message" + }; + + await context.mutations.createFulfillmentMethod(context.getInternalContext(), { shopId, fulfillmentTypeId, method }); + return { method }; +} diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/util/checkAndCreateFulfillmentMethod.test.js b/packages/api-plugin-fulfillment-method-shipping-ups/src/util/checkAndCreateFulfillmentMethod.test.js new file mode 100644 index 00000000000..33916db229b --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-ups/src/util/checkAndCreateFulfillmentMethod.test.js @@ -0,0 +1,87 @@ +import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; +import checkAndCreateFulfillmentMethod from "./checkAndCreateFulfillmentMethod.js"; + +test("should call createFulfillmentMethod mutation", async () => { + const shopId = "SHOP_ID"; + const fulfillment = { + _id: "fulfillment123" + }; + const fulfillmentTypeId = fulfillment._id; + const groupInfo = { + name: "Shipping Provider", + shopId: "SHOP_ID", + provider: { + enabled: true, + label: "Shipping", + name: "shipping" + }, + fulfillmentType: "shipping" + }; + const method = { + name: "ups", + label: "Shipping via UPS", + fulfillmentTypes: ["shipping"], + group: "Ground", + cost: 0, + handling: 0, + rate: 0, + enabled: true, + fulfillmentMethod: "ups", + displayMessageMethod: "Placeholder for display message" + }; + + mockContext.getInternalContext = () => ({ + ...mockContext, + account: null, + accountId: null, + isInternalCall: true, + user: null, + userHasPermission: async () => true, + userId: null, + validatePermissions: async () => undefined + }); + + mockContext.collections.Fulfillment = { + findOne: jest.fn().mockReturnValueOnce(Promise.resolve(groupInfo)) + }; + mockContext.mutations.createFulfillmentMethod = jest.fn().mockName("createFulfillmentMethod").mockReturnValueOnce(Promise.resolve(method)); + + await checkAndCreateFulfillmentMethod(mockContext, { shopId, fulfillmentTypeId, method }); + expect(mockContext.mutations.createFulfillmentMethod).toHaveBeenCalled(); +}); + +test("should throw error and NOT call createFulfillmentMethod mutation", async () => { + const fulfillment = { _id: "fulfillment123" }; + const shopId = "SHOP_ID"; + const fulfillmentTypeId = fulfillment._id; + const method = { + name: "ups", + label: "Shipping via UPS", + fulfillmentTypes: ["shipping"], + group: "Ground", + cost: 0, + handling: 0, + rate: 0, + enabled: true, + fulfillmentMethod: "ups", + displayMessageMethod: "Placeholder for display message" + }; + + mockContext.getInternalContext = () => ({ + ...mockContext, + account: null, + accountId: null, + isInternalCall: true, + user: null, + userHasPermission: async () => true, + userId: null, + validatePermissions: async () => undefined + }); + + mockContext.collections.Fulfillment = { + findOne: jest.fn().mockReturnValueOnce(undefined) + }; + mockContext.mutations.createFulfillmentMethod = jest.fn().mockName("createFulfillmentMethod").mockReturnValueOnce(Promise.resolve(method)); + + await expect(checkAndCreateFulfillmentMethod(mockContext, { shopId, fulfillmentTypeId, method })).rejects.toThrowErrorMatchingSnapshot(); +}); diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/util/sampleTest.test.js b/packages/api-plugin-fulfillment-method-shipping-ups/src/util/sampleTest.test.js deleted file mode 100644 index d94db67445e..00000000000 --- a/packages/api-plugin-fulfillment-method-shipping-ups/src/util/sampleTest.test.js +++ /dev/null @@ -1,3 +0,0 @@ -test("sample test", async () => { - expect(1 + 1).toBe(2); -}); diff --git a/packages/api-plugin-fulfillment-type-shipping/src/checkAndCreateFulfillmentType.js b/packages/api-plugin-fulfillment-type-shipping/src/checkAndCreateFulfillmentType.js new file mode 100644 index 00000000000..d5db578a9f1 --- /dev/null +++ b/packages/api-plugin-fulfillment-type-shipping/src/checkAndCreateFulfillmentType.js @@ -0,0 +1,27 @@ + +/** + * @summary Called on startup to create the root entry of this fulfillment type in Fulfillment collection + * @param {Object} context Startup context + * @param {String} shopId Shop ID + * @returns {Boolean} true if entry exist or insert success else false + */ +export default async function checkAndCreateFulfillmentType(context, shopId) { + const { collections } = context; + const { Fulfillment } = collections; + + const shippingRecord = await Fulfillment.findOne({ fulfillmentType: "shipping", shopId }); + if (!shippingRecord) { + const groupInfo = { + name: "Shipping Provider", + shopId, + provider: { + enabled: true, + label: "Shipping", + name: "shipping" + }, + fulfillmentType: "shipping" + }; + await context.mutations.createFulfillmentType(context.getInternalContext(), groupInfo); + } + return true; +} diff --git a/packages/api-plugin-fulfillment-type-shipping/src/checkAndCreateFulfillmentType.test.js b/packages/api-plugin-fulfillment-type-shipping/src/checkAndCreateFulfillmentType.test.js new file mode 100644 index 00000000000..cbfe2fd976d --- /dev/null +++ b/packages/api-plugin-fulfillment-type-shipping/src/checkAndCreateFulfillmentType.test.js @@ -0,0 +1,61 @@ +import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; +import checkAndCreateFulfillmentType from "./checkAndCreateFulfillmentType.js"; + +test("should NOT call createFulfillmentType mutation", async () => { + const shopId = "SHOP_ID"; + const fulfillment = { + _id: "fulfillment123" + }; + const groupInfo = { + name: "Shipping Provider", + shopId: "SHOP_ID", + provider: { + enabled: true, + label: "Shipping", + name: "shipping" + }, + fulfillmentType: "shipping" + }; + mockContext.getInternalContext = () => ({ + ...mockContext, + account: null, + accountId: null, + isInternalCall: true, + user: null, + userHasPermission: async () => true, + userId: null, + validatePermissions: async () => undefined + }); + + mockContext.collections.Fulfillment = { + findOne: jest.fn().mockReturnValueOnce(Promise.resolve(groupInfo)) + }; + mockContext.mutations.createFulfillmentType = jest.fn().mockName("createFulfillmentType").mockReturnValueOnce(Promise.resolve(fulfillment)); + + await checkAndCreateFulfillmentType(mockContext, shopId); + expect(mockContext.mutations.createFulfillmentType).not.toHaveBeenCalled(); +}); + +test("should call createFulfillmentType mutation", async () => { + const fulfillment = { _id: "fulfillment123" }; + const shopId = "SHOP_ID"; + + mockContext.getInternalContext = () => ({ + ...mockContext, + account: null, + accountId: null, + isInternalCall: true, + user: null, + userHasPermission: async () => true, + userId: null, + validatePermissions: async () => undefined + }); + + mockContext.collections.Fulfillment = { + findOne: jest.fn().mockReturnValueOnce(undefined) + }; + mockContext.mutations.createFulfillmentType = jest.fn().mockName("createFulfillmentType").mockReturnValueOnce(Promise.resolve(fulfillment)); + + await checkAndCreateFulfillmentType(mockContext, shopId); + expect(mockContext.mutations.createFulfillmentType).toHaveBeenCalled(); +}); diff --git a/packages/api-plugin-fulfillment-type-shipping/src/sampleTest.test.js b/packages/api-plugin-fulfillment-type-shipping/src/sampleTest.test.js deleted file mode 100644 index d94db67445e..00000000000 --- a/packages/api-plugin-fulfillment-type-shipping/src/sampleTest.test.js +++ /dev/null @@ -1,3 +0,0 @@ -test("sample test", async () => { - expect(1 + 1).toBe(2); -}); diff --git a/packages/api-plugin-fulfillment-type-shipping/src/startup.js b/packages/api-plugin-fulfillment-type-shipping/src/startup.js index 23a73bfaf2a..e3cf4bed319 100644 --- a/packages/api-plugin-fulfillment-type-shipping/src/startup.js +++ b/packages/api-plugin-fulfillment-type-shipping/src/startup.js @@ -1,4 +1,5 @@ -import Random from "@reactioncommerce/random"; +import ReactionError from "@reactioncommerce/reaction-error"; +import checkAndCreateFulfillmentType from "./checkAndCreateFulfillmentType.js"; /** * @summary Called on startup to create the root entry of this fulfillment type in Fulfillment collection @@ -7,9 +8,6 @@ import Random from "@reactioncommerce/random"; * @returns {undefined} */ export default async function fulfillmentTypeShippingStartup(context) { - const { collections } = context; - const { Fulfillment } = collections; - context.appEvents.on("afterShopCreate", async (payload) => { const { shop } = payload; const shopId = shop._id; @@ -17,21 +15,9 @@ export default async function fulfillmentTypeShippingStartup(context) { // We do not have validatePermissions in context during this startup stage, hence commenting below // await context.validatePermissions("reaction:legacy:fulfillmentTypes", "read", { shopId }); - const shippingRecord = await Fulfillment.findOne({ fulfillmentType: "shipping", shopId }); - if (!shippingRecord) { - const groupInfo = { - _id: Random.id(), - name: "Shipping Provider", - shopId, - provider: { - enabled: true, - label: "Shipping", - name: "shipping" - }, - fulfillmentType: "shipping" - }; - - await context.mutations.createFulfillmentType(context.getInternalContext(), groupInfo); + const insertSuccess = await checkAndCreateFulfillmentType(context, shopId); + if (!insertSuccess) { + throw new ReactionError("server-error", "Error in creating fulfillment type"); } }); } From 3156baec688799593433c7a2f2511e129a3be191 Mon Sep 17 00:00:00 2001 From: Sujith Date: Mon, 24 Oct 2022 13:44:28 +0530 Subject: [PATCH 18/71] fix: filename fixed Signed-off-by: Sujith --- ...test.js => getFulfillmentMethodsWithQuotesShippingUPS.test.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/api-plugin-fulfillment-method-shipping-ups/src/{getFulfillmentMethodsWithQuotesPickupStore.test.js => getFulfillmentMethodsWithQuotesShippingUPS.test.js} (100%) diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/getFulfillmentMethodsWithQuotesPickupStore.test.js b/packages/api-plugin-fulfillment-method-shipping-ups/src/getFulfillmentMethodsWithQuotesShippingUPS.test.js similarity index 100% rename from packages/api-plugin-fulfillment-method-shipping-ups/src/getFulfillmentMethodsWithQuotesPickupStore.test.js rename to packages/api-plugin-fulfillment-method-shipping-ups/src/getFulfillmentMethodsWithQuotesShippingUPS.test.js From 0c30b61cda7520f07eecae68c989ed340370c008 Mon Sep 17 00:00:00 2001 From: Sujith Date: Thu, 27 Oct 2022 23:49:52 +0530 Subject: [PATCH 19/71] fix: review comment fixes Signed-off-by: Sujith --- .../.gitignore | 61 -------------- .../README.md | 45 ++-------- ...llmentMethodsWithQuotesShippingFlatRate.js | 3 +- .../src/index.js | 7 +- .../createFlatRateFulfillmentMethod.js | 9 +- .../createFlatRateFulfillmentRestriction.js | 9 +- .../deleteFlatRateFulfillmentMethod.js | 7 +- .../deleteFlatRateFulfillmentRestriction.js | 3 +- .../updateFlatRateFulfillmentMethod.js | 9 +- .../updateFlatRateFulfillmentRestriction.js | 9 +- .../src/queries/flatRateFulfillmentMethod.js | 5 +- .../src/queries/flatRateFulfillmentMethods.js | 3 +- .../getFlatRateFulfillmentRestriction.js | 5 +- .../getFlatRateFulfillmentRestrictions.js | 5 +- .../deleteFlatRateFulfillmentMethod.js | 2 +- .../src/simpleSchemas.js | 82 +++++++++++++++++++ .../src/util/filterShippingMethods.test.js | 40 +++++++++ .../src/util/methodSchema.js | 25 ------ .../src/util/restrictionSchema.js | 63 -------------- .../src/util/validateOrderMethodsflatrate.js | 19 +++-- .../.gitignore | 61 -------------- .../README.md | 44 ++-------- .../package.json | 8 +- ...ntMethodsWithQuotesShippingDynamicRate.js} | 18 ++-- ...hodsWithQuotesShippingDynamicRate.test.js} | 26 +++--- .../src/index.js | 17 ++-- .../src/preStartup.js | 8 +- .../src/schemas/schema.graphql | 8 +- .../src/simpleSchemas.js | 10 +-- .../src/startup.js | 2 +- ...eckAndCreateFulfillmentMethod.test.js.snap | 2 +- ...lateUPSRate.js => calculateDynamicRate.js} | 12 +-- .../util/checkAndCreateFulfillmentMethod.js | 11 ++- .../checkAndCreateFulfillmentMethod.test.js | 12 +-- .../util/validateOrderMethodsDynamicRate.js | 21 +++++ .../src/util/validateOrderMethodsups.js | 22 ----- .../.gitignore | 61 -------------- .../README.md | 42 ++-------- .../src/checkAndCreateFulfillmentType.js | 3 +- 39 files changed, 274 insertions(+), 525 deletions(-) delete mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/.gitignore delete mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/methodSchema.js delete mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/restrictionSchema.js delete mode 100644 packages/api-plugin-fulfillment-method-shipping-ups/.gitignore rename packages/api-plugin-fulfillment-method-shipping-ups/src/{getFulfillmentMethodsWithQuotesShippingUPS.js => getFulfillmentMethodsWithQuotesShippingDynamicRate.js} (80%) rename packages/api-plugin-fulfillment-method-shipping-ups/src/{getFulfillmentMethodsWithQuotesShippingUPS.test.js => getFulfillmentMethodsWithQuotesShippingDynamicRate.test.js} (73%) rename packages/api-plugin-fulfillment-method-shipping-ups/src/util/{calculateUPSRate.js => calculateDynamicRate.js} (78%) create mode 100644 packages/api-plugin-fulfillment-method-shipping-ups/src/util/validateOrderMethodsDynamicRate.js delete mode 100644 packages/api-plugin-fulfillment-method-shipping-ups/src/util/validateOrderMethodsups.js delete mode 100644 packages/api-plugin-fulfillment-type-shipping/.gitignore diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/.gitignore b/packages/api-plugin-fulfillment-method-shipping-flat-rate/.gitignore deleted file mode 100644 index ad46b30886f..00000000000 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/.gitignore +++ /dev/null @@ -1,61 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# TypeScript v1 declaration files -typings/ - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file -.env - -# next.js build output -.next diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/README.md b/packages/api-plugin-fulfillment-method-shipping-flat-rate/README.md index 14388ca70df..7bec8e1ed84 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/README.md +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/README.md @@ -1,41 +1,14 @@ # api-plugin-fulfillment-method-shipping-flat-rate -[![npm (scoped)](https://img.shields.io/npm/v/@reactioncommerce/api-plugin-fulfillment-method-shipping-flat-rate.svg)](https://www.npmjs.com/package/@reactioncommerce/api-plugin-fulfillment-method-shipping-flat-rate) -[![CircleCI](https://circleci.com/gh/reactioncommerce/api-plugin-fulfillment-method-shipping-flat-rate.svg?style=svg)](https://circleci.com/gh/reactioncommerce/api-plugin-fulfillment-method-shipping-flat-rate) - -## Summary - -This plugin provides the functionalities of a sample fulfillment method flat-rate under the fulfillment type of shipping. - -## Included in this fulfillment-method-shipping-flat-rate plugin - -### `src/` - -The `src` folder with all the plugin files. - -### `.gitignore` - -A basic `gitignore` file - -### `babel.config.cjs` - -If your plugin includes linting and tests, this file is required to allow esmodules to run correctly. - -### `jest.config.cjs` - -If your plugin includes tests, this file is required to allow esmodules to run correctly. You'll need to update the `transformIgnorePatterns` and `moduleNameMapper` sections to include any esmodule `npm` packages used in your plugin. - -### `License.md` - -If your plugin uses `Apache 2` licensing, you can leave this file as-is. If another type of licensing is used, you need to update this file, and the README, accordingly. - -### `package.json` - -The provided `package.json` is set up to install all needed packages and config for linting, testing, and semantic-release. You'll need to update the `name`, `description`, and add any new dependencies your plugin files use. - -### `index.js` - -The entrypoint file for your npm package, will most likely just export your plugin registration from the `src` folder. +This is a fulfillment-method plugin which which works along with the base `api-plugin-fulfillment` and the ff-type plugin `api-plugin-fulfillment-type-shipping`. This plugin actually implements all the functionality associated with this fulfillment-method. It can work along-side other fulfillment-methods under the same ff-type. + +This main features/functionalities of this plugin includes the following: +* getFulfillmentMethodsWithQuotesShippingFlatRate - returns the quote or equivalent details for the method when called from base ff plugin +* preStartup - extends the union of "methodAdditionalData" with data structure specific to FlatRate (dummy and can be changed) +* create/update/delete FlatRateFulfillmentMethods +* create/update/delete FlatRateFulfillmentRestrictions +* getFlatRateFulfillmentMethod(s) +* getFlatRateFulfillmentRestriction(s) ## Developer Certificate of Origin We use the [Developer Certificate of Origin (DCO)](https://developercertificate.org/) in lieu of a Contributor License Agreement for all contributions to Reaction Commerce open source projects. We request that contributors agree to the terms of the DCO and indicate that agreement by signing all commits made to Reaction Commerce projects by adding a line with your name and email address to every Git commit message contributed: diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/getFulfillmentMethodsWithQuotesShippingFlatRate.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/getFulfillmentMethodsWithQuotesShippingFlatRate.js index b59da101399..e48c82ae482 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/getFulfillmentMethodsWithQuotesShippingFlatRate.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/getFulfillmentMethodsWithQuotesShippingFlatRate.js @@ -20,8 +20,7 @@ const fulfillmentMethodName = "flatRate"; * @private */ export default async function getFulfillmentMethodsWithQuotesShippingFlatRate(context, commonOrder, previousQueryResults = []) { - const { collections } = context; - const { Fulfillment } = collections; + const { collections: { Fulfillment } } = context; const [rates = [], retrialTargets = []] = previousQueryResults; const currentMethodInfo = { packageName }; diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/index.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/index.js index cf3c7fd12c8..05f4c06ce70 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/index.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/index.js @@ -1,6 +1,6 @@ import { createRequire } from "module"; import getFulfillmentMethodsWithQuotesShippingFlatRate from "./getFulfillmentMethodsWithQuotesShippingFlatRate.js"; -import validateOrderMethodsflatRate from "./util/validateOrderMethodsflatrate.js"; +import validateOrderMethodsFlatRate from "./util/validateOrderMethodsFlatRate.js"; import resolvers from "./resolvers/index.js"; import mutations from "./mutations/index.js"; import policies from "./policies.json"; @@ -41,9 +41,8 @@ export default async function register(app) { }, functionsByType: { preStartup: [fulfillmentMethodShippingFlatRatePreStartup], - validateOrderMethods: [validateOrderMethodsflatRate], - getFulfillmentMethodsWithQuotes: [getFulfillmentMethodsWithQuotesShippingFlatRate], - getFulfillmentMethodsWithQuotesShipping: [getFulfillmentMethodsWithQuotesShippingFlatRate] + validateOrderMethods: [{ key: "flatRate", handler: validateOrderMethodsFlatRate }], + getFulfillmentMethodsWithQuotes: [{ key: "shipping", handler: getFulfillmentMethodsWithQuotesShippingFlatRate }] }, shopSettingsConfig: { isShippingRatesFulfillmentEnabled: { diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/createFlatRateFulfillmentMethod.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/createFlatRateFulfillmentMethod.js index 1b3059dbf68..615b6c7cf45 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/createFlatRateFulfillmentMethod.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/createFlatRateFulfillmentMethod.js @@ -1,7 +1,7 @@ import SimpleSchema from "simpl-schema"; import Random from "@reactioncommerce/random"; import ReactionError from "@reactioncommerce/reaction-error"; -import methodSchema from "../util/methodSchema.js"; +import { methodSchema } from "../simpleSchemas.js"; const inputSchema = new SimpleSchema({ method: methodSchema, @@ -9,19 +9,18 @@ const inputSchema = new SimpleSchema({ }); /** - * @method createFlatRateFulfillmentMethodMutation + * @method createFlatRateFulfillmentMethod * @summary Creates a flat rate fulfillment method * @param {Object} context - an object containing the per-request state * @param {Object} input - Input (see SimpleSchema) * @returns {Promise} An object with a `method` property containing the created method */ -export default async function createFlatRateFulfillmentMethodMutation(context, input) { +export default async function createFlatRateFulfillmentMethod(context, input) { const cleanedInput = inputSchema.clean(input); // add default values and such inputSchema.validate(cleanedInput); const { method: inputMethod, shopId } = cleanedInput; - const { collections } = context; - const { Fulfillment } = collections; + const { collections: { Fulfillment } } = context; const method = { ...inputMethod }; await context.validatePermissions("reaction:legacy:fulfillmentMethods", "create", { shopId }); diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/createFlatRateFulfillmentRestriction.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/createFlatRateFulfillmentRestriction.js index 5fe7e7889ce..7d62f643bcf 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/createFlatRateFulfillmentRestriction.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/createFlatRateFulfillmentRestriction.js @@ -1,7 +1,7 @@ import SimpleSchema from "simpl-schema"; import Random from "@reactioncommerce/random"; import ReactionError from "@reactioncommerce/reaction-error"; -import restrictionSchema from "../util/restrictionSchema.js"; +import { restrictionSchema } from "../simpleSchemas.js"; const inputSchema = new SimpleSchema({ restriction: restrictionSchema, @@ -9,19 +9,18 @@ const inputSchema = new SimpleSchema({ }); /** - * @method createFlatRateFulfillmentRestrictionMutation + * @method createFlatRateFulfillmentRestriction * @summary Creates a flat rate fulfillment restriction * @param {Object} context - an object containing the per-request state * @param {Object} input - Input (see SimpleSchema) * @returns {Promise} An object with a `restriction` property containing the created restriction */ -export default async function createFlatRateFulfillmentRestrictionMutation(context, input) { +export default async function createFlatRateFulfillmentRestriction(context, input) { const cleanedInput = inputSchema.clean(input); // add default values and such inputSchema.validate(cleanedInput); const { restriction, shopId } = cleanedInput; - const { collections } = context; - const { FulfillmentRestrictions } = collections; + const { collections: { FulfillmentRestrictions } } = context; await context.validatePermissions("reaction:legacy:fulfillmentRestrictions", "create", { shopId }); diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/deleteFlatRateFulfillmentMethod.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/deleteFlatRateFulfillmentMethod.js index c114c722a82..8d9ac9b7070 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/deleteFlatRateFulfillmentMethod.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/deleteFlatRateFulfillmentMethod.js @@ -7,18 +7,17 @@ const inputSchema = new SimpleSchema({ }); /** - * @method deleteFlatRateFulfillmentMethodMutation + * @method deleteFlatRateFulfillmentMethod * @summary deletes a flat rate fulfillment method * @param {Object} context - an object containing the per-request state * @param {Object} input - Input (see SimpleSchema) * @returns {Promise} An object with a `method` property containing the deleted method */ -export default async function deleteFlatRateFulfillmentMethodMutation(context, input) { +export default async function deleteFlatRateFulfillmentMethod(context, input) { inputSchema.validate(input); const { methodId, shopId } = input; - const { collections } = context; - const { Fulfillment } = collections; + const { collections: { Fulfillment } } = context; await context.validatePermissions(`reaction:legacy:fulfillmentMethods:${methodId}`, "delete", { shopId }); diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/deleteFlatRateFulfillmentRestriction.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/deleteFlatRateFulfillmentRestriction.js index f8773de022f..422176806d2 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/deleteFlatRateFulfillmentRestriction.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/deleteFlatRateFulfillmentRestriction.js @@ -17,8 +17,7 @@ export default async function deleteFlatRateFulfillmentRestriction(context, inpu inputSchema.validate(input); const { restrictionId, shopId } = input; - const { collections } = context; - const { FulfillmentRestrictions } = collections; + const { collections: { FulfillmentRestrictions } } = context; await context.validatePermissions(`reaction:legacy:fulfillmentRestrictions:${restrictionId}`, "delete", { shopId }); diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentMethod.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentMethod.js index aa4bb26f997..83f4c63618b 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentMethod.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentMethod.js @@ -1,6 +1,6 @@ import SimpleSchema from "simpl-schema"; import ReactionError from "@reactioncommerce/reaction-error"; -import methodSchema from "../util/methodSchema.js"; +import { methodSchema } from "../simpleSchemas.js"; const inputSchema = new SimpleSchema({ method: methodSchema, @@ -9,19 +9,18 @@ const inputSchema = new SimpleSchema({ }); /** - * @method updateFlatRateFulfillmentMethodMutation + * @method updateFlatRateFulfillmentMethod * @summary updates a flat rate fulfillment method * @param {Object} context - an object containing the per-request state * @param {Object} input - Input (see SimpleSchema) * @returns {Promise} An object with a `method` property containing the updated method */ -export default async function updateFlatRateFulfillmentMethodMutation(context, input) { +export default async function updateFlatRateFulfillmentMethod(context, input) { const cleanedInput = inputSchema.clean(input); // add default values and such inputSchema.validate(cleanedInput); const { method: inputMethod, methodId, shopId } = cleanedInput; - const { collections } = context; - const { Fulfillment } = collections; + const { collections: { Fulfillment } } = context; const method = { ...inputMethod }; if (!methodId) throw new ReactionError("invalid-parameter", "Method ID to be updated not provided"); diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentRestriction.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentRestriction.js index ca75678a3ba..c7378bdb000 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentRestriction.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentRestriction.js @@ -1,6 +1,6 @@ import SimpleSchema from "simpl-schema"; import ReactionError from "@reactioncommerce/reaction-error"; -import restrictionSchema from "../util/restrictionSchema.js"; +import { restrictionSchema } from "../simpleSchemas.js"; const inputSchema = new SimpleSchema({ restrictionId: String, @@ -10,19 +10,18 @@ const inputSchema = new SimpleSchema({ /** - * @method updateFlatRateFulfillmentRestrictionMutation + * @method updateFlatRateFulfillmentRestriction * @summary updates a flat rate fulfillment method * @param {Object} context - an object containing the per-request state * @param {Object} input - Input (see SimpleSchema) * @returns {Promise} An object with a `restriction` property containing the updated method */ -export default async function updateFlatRateFulfillmentRestrictionMutation(context, input) { +export default async function updateFlatRateFulfillmentRestriction(context, input) { const cleanedInput = inputSchema.clean(input); // add default values and such inputSchema.validate(cleanedInput); const { restriction, restrictionId, shopId } = cleanedInput; - const { collections } = context; - const { FulfillmentRestrictions } = collections; + const { collections: { FulfillmentRestrictions } } = context; await context.validatePermissions(`reaction:legacy:fulfillmentRestrictions:${restrictionId}`, "update", { shopId }); diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/flatRateFulfillmentMethod.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/flatRateFulfillmentMethod.js index 0f80732b684..234f2b997c8 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/flatRateFulfillmentMethod.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/flatRateFulfillmentMethod.js @@ -7,11 +7,10 @@ * @param {Object} input - Request input * @param {String} input.methodId - The fulfillment method id * @param {String} input.shopId - The shop id of the fulfillment method - * @returns {Promise} Mongo cursor + * @returns {Promise} Object */ export default async function flatRateFulfillmentMethod(context, input) { - const { collections } = context; - const { Fulfillment } = collections; + const { collections: { Fulfillment } } = context; const { methodId, shopId } = input; await context.validatePermissions("reaction:legacy:fulfillmentMethods", "read", { shopId }); diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/flatRateFulfillmentMethods.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/flatRateFulfillmentMethods.js index 076a2de158e..42695aa6b8b 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/flatRateFulfillmentMethods.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/flatRateFulfillmentMethods.js @@ -9,8 +9,7 @@ * @returns {Promise} Mongo cursor */ export default async function flatRateFulfillmentMethods(context, input) { - const { collections } = context; - const { Fulfillment } = collections; + const { collections: { Fulfillment } } = context; const { shopId } = input; const fulfillmentTypeName = "shipping"; const fulfillmentMethodName = "flatRate"; diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/getFlatRateFulfillmentRestriction.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/getFlatRateFulfillmentRestriction.js index 19ea42f331b..71d0d10cb27 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/getFlatRateFulfillmentRestriction.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/getFlatRateFulfillmentRestriction.js @@ -1,7 +1,7 @@ /** * @name getFlatRateFulfillmentRestriction * @method - * @memberof Fulfillment/NoMeteorQueries + * @memberof Fulfillment * @summary Query the FulfillmentRestrictions collection for restrictions with the provided shopId * @param {Object} context - an object containing the per-request state * @param {Object} params - request parameters @@ -9,8 +9,7 @@ * @returns {Promise|undefined} - A restrictions document, if one is found */ export default async function getFlatRateFulfillmentRestriction(context, { restrictionId, shopId } = {}) { - const { collections } = context; - const { FulfillmentRestrictions } = collections; + const { collections: { FulfillmentRestrictions } } = context; await context.validatePermissions(`reaction:legacy:fulfillmentRestrictions:${restrictionId}`, "read", { shopId }); diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/getFlatRateFulfillmentRestrictions.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/getFlatRateFulfillmentRestrictions.js index 116dac58672..9d352db7438 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/getFlatRateFulfillmentRestrictions.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/getFlatRateFulfillmentRestrictions.js @@ -1,7 +1,7 @@ /** * @name getFlatRateFulfillmentRestrictions * @method - * @memberof Fulfillment/NoMeteorQueries + * @memberof Fulfillment * @summary Query the FulfillmentRestrictions collection for restrictions with the provided shopId * @param {Object} context - an object containing the per-request state * @param {Object} params - request parameters @@ -9,8 +9,7 @@ * @returns {Promise|undefined} - A restrictions document, if one is found */ export default async function getFlatRateFulfillmentRestrictions(context, { shopId } = {}) { - const { collections } = context; - const { FulfillmentRestrictions } = collections; + const { collections: { FulfillmentRestrictions } } = context; await context.validatePermissions("reaction:legacy:fulfillmentRestrictions", "read", { shopId }); diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Mutation/deleteFlatRateFulfillmentMethod.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Mutation/deleteFlatRateFulfillmentMethod.js index 92e247119aa..af1813d9550 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Mutation/deleteFlatRateFulfillmentMethod.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Mutation/deleteFlatRateFulfillmentMethod.js @@ -5,7 +5,7 @@ import deleteFlatRateFulfillmentMethodMutation from "../../mutations/deleteFlatR * @name Mutation/deleteFlatRateFulfillmentMethod * @method * @memberof Fulfillment/GraphQL - * @summary resolver for the deleteFlatRateFulfillmentMethod GraphQL mutation + * @summary resolver for the deleteFlatRateFulfillment GraphQL mutation * @param {Object} parentResult - unused * @param {Object} args.input - an object of all mutation arguments that were sent by the client * @param {String} args.input.methodId - The ID of the method you want to delete diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/simpleSchemas.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/simpleSchemas.js index e29df9a7541..78dfab8931c 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/simpleSchemas.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/simpleSchemas.js @@ -15,3 +15,85 @@ export const MethodFlatRateData = new SimpleSchema({ optional: true } }); + +export const methodSchema = new SimpleSchema({ + "cost": { + type: Number, + optional: true + }, + "fulfillmentTypes": { + type: Array, + minCount: 1 + }, + "fulfillmentTypes.$": String, + "group": String, + "handling": Number, + "isEnabled": Boolean, + "label": String, + "name": String, + "fulfillmentMethod": { + type: String, + optional: true + }, + "rate": Number +}); + +/** + * @name Attributes + * @memberof Schemas + * @type {SimpleSchema} + * @property {String} property required + * @property {String} value required + * @property {String} propertyType required + * @property {String} operator required + */ +export const Attributes = new SimpleSchema({ + property: String, + value: String, + propertyType: String, + operator: String +}); + +/** + * @name Destination + * @memberof Schemas + * @type {SimpleSchema} + * @property {String} country optional + * @property {String} region optional + * @property {String} postal optional + */ +export const Destination = new SimpleSchema({ + "country": { + type: Array, + optional: true + }, + "country.$": String, + "region": { + type: Array, + optional: true + }, + "region.$": String, + "postal": { + type: Array, + optional: true + }, + "postal.$": String +}); + +export const restrictionSchema = new SimpleSchema({ + "methodIds": { + type: Array, + optional: true + }, + "methodIds.$": String, + "type": String, + "attributes": { + type: Array, + optional: true + }, + "attributes.$": Attributes, + "destination": { + type: Destination, + optional: true + } +}); diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/filterShippingMethods.test.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/filterShippingMethods.test.js index 682622f7c83..d95b7433fdf 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/filterShippingMethods.test.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/filterShippingMethods.test.js @@ -360,6 +360,46 @@ test("allow method - multiple attributes but only 1 meets criteria to deny", asy expect(allowedMethods).toEqual(mockShippingMethod); }); +test("allow method - address pattern provided matches, when no match for country", async () => { + // Shipping Location: US, CA, 90405 + + const mockMethodRestrictions = [ + { + _id: "allow001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "allow", + destination: { + country: [ + "USA" + ], + addressPattern: "California", + addressPatternShouldMatch: true + } + }, + { + _id: "deny001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "deny", + destination: { + region: [ + "AK", + "HI" + ] + } + } + ]; + + mockContext.collections.FulfillmentRestrictions.toArray.mockReturnValue(Promise.resolve(mockMethodRestrictions)); + + const allowedMethods = await filterShippingMethods(mockContext, mockShippingMethod, mockHydratedOrder); + + expect(allowedMethods).toEqual(mockShippingMethod); +}); + /* * Tests with location restrictions AND attribute restrictions that deny method diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/methodSchema.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/methodSchema.js deleted file mode 100644 index 4b80cb36723..00000000000 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/methodSchema.js +++ /dev/null @@ -1,25 +0,0 @@ -import SimpleSchema from "simpl-schema"; - -const methodSchema = new SimpleSchema({ - "cost": { - type: Number, - optional: true - }, - "fulfillmentTypes": { - type: Array, - minCount: 1 - }, - "fulfillmentTypes.$": String, - "group": String, - "handling": Number, - "isEnabled": Boolean, - "label": String, - "name": String, - "fulfillmentMethod": { - type: String, - optional: true - }, - "rate": Number -}); - -export default methodSchema; diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/restrictionSchema.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/restrictionSchema.js deleted file mode 100644 index 01c18ae28b5..00000000000 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/restrictionSchema.js +++ /dev/null @@ -1,63 +0,0 @@ -import SimpleSchema from "simpl-schema"; - -/** - * @name Attributes - * @memberof Schemas - * @type {SimpleSchema} - * @property {String} property required - * @property {String} value required - * @property {String} propertyType required - * @property {String} operator required - */ -export const Attributes = new SimpleSchema({ - property: String, - value: String, - propertyType: String, - operator: String -}); - -/** - * @name Destination - * @memberof Schemas - * @type {SimpleSchema} - * @property {String} country optional - * @property {String} region optional - * @property {String} postal optional - */ -export const Destination = new SimpleSchema({ - "country": { - type: Array, - optional: true - }, - "country.$": String, - "region": { - type: Array, - optional: true - }, - "region.$": String, - "postal": { - type: Array, - optional: true - }, - "postal.$": String -}); - -const restrictionSchema = new SimpleSchema({ - "methodIds": { - type: Array, - optional: true - }, - "methodIds.$": String, - "type": String, - "attributes": { - type: Array, - optional: true - }, - "attributes.$": Attributes, - "destination": { - type: Destination, - optional: true - } -}); - -export default restrictionSchema; diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/validateOrderMethodsflatrate.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/validateOrderMethodsflatrate.js index 54c9543954e..069b1dd290d 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/validateOrderMethodsflatrate.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/validateOrderMethodsflatrate.js @@ -5,19 +5,20 @@ * @param {Object[]} validationResults - Existing validation Results * @returns {Object[]} validationResults - with the validation details populated */ -export default function validateOrderMethodsflatrate(context, commonOrder, validationResults = []) { +export default function validateOrderMethodsFlatRate(context, commonOrder, validationResults = []) { // Add the code here to verify the data requirements of this particular plugin // Sample template for returning any identified errors is as below // If no errors, return the "validationResults" array as it is. + // Commented out below code to avoid placeOrder getting this error. Code retained for demo purpose. - const validationResult = { - errorName: "invalid", - errorType: "ReactionError", - errorField: "Flatrate shipping - some field", - fieldValue: "field-value", - errorMessage: "Customer address not available to decide shipping address" - }; - validationResults.push(validationResult); + // const validationResult = { + // errorName: "invalid", + // errorType: "ReactionError", + // errorField: "Flatrate shipping - some field", + // fieldValue: "field-value", + // errorMessage: "Customer address not available to decide shipping address" + // }; + // validationResults.push(validationResult); return validationResults; } diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/.gitignore b/packages/api-plugin-fulfillment-method-shipping-ups/.gitignore deleted file mode 100644 index ad46b30886f..00000000000 --- a/packages/api-plugin-fulfillment-method-shipping-ups/.gitignore +++ /dev/null @@ -1,61 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# TypeScript v1 declaration files -typings/ - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file -.env - -# next.js build output -.next diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/README.md b/packages/api-plugin-fulfillment-method-shipping-ups/README.md index 3ece899287f..09b74e8d31f 100644 --- a/packages/api-plugin-fulfillment-method-shipping-ups/README.md +++ b/packages/api-plugin-fulfillment-method-shipping-ups/README.md @@ -1,42 +1,16 @@ -# api-plugin-fulfillment-method-shipping-ups +# api-plugin-fulfillment-method-shipping-dynamic-rate -[![npm (scoped)](https://img.shields.io/npm/v/@reactioncommerce/api-plugin-fulfillment-method-shipping-ups.svg)](https://www.npmjs.com/package/@reactioncommerce/api-plugin-fulfillment-method-shipping-ups) -[![CircleCI](https://circleci.com/gh/reactioncommerce/api-plugin-fulfillment-method-shipping-ups.svg?style=svg)](https://circleci.com/gh/reactioncommerce/api-plugin-fulfillment-method-shipping-ups) +This is a fulfillment-method plugin which which works along with the base `api-plugin-fulfillment` and the ff-type plugin `api-plugin-fulfillment-type-shipping`. This plugin actually implements all the functionality associated with this fulfillment-method. It can work along-side other fulfillment-methods under the same ff-type. +This main features/functionalities of this plugin includes the following: +* getFulfillmentMethodsWithQuotesShippingDynamicRate - returns the quote or equivalent details for the method when called from base ff plugin +* preStartup - extends the union of "methodAdditionalData" with data structure specific to Dynamic Rate +* startup - Inserts the required ff-method entry into Fulfillment collection +* util/checkAndCreateFulfillmentMethod - confirms existing ff-type entry and adds a new ff-method under it. +* util/calculateDynamicRateRate - dummy function to simulate api providing dynamic rate specific info while returning quotes. +* util/validateOrderMethodsDynamicRate - dummy function to simulate dynamic rate specific validations done. Called by prepareOrder.js -## Summary -This plugin Implements Shipping-UPS as a fulfillment method under the type shipping. - -## Included in this fulfillment-method-shipping-ups plugin - -### `src/` - -The `src` folder contains all the plugin files. - -### `.gitignore` - -A basic `gitignore` file - -### `babel.config.cjs` - -If your plugin includes linting and tests, this file is required to allow esmodules to run correctly. - -### `jest.config.cjs` - -If your plugin includes tests, this file is required to allow esmodules to run correctly. You'll need to update the `transformIgnorePatterns` and `moduleNameMapper` sections to include any esmodule `npm` packages used in your plugin. - -### `License.md` - -If your plugin uses `Apache 2` licensing, you can leave this file as-is. If another type of licensing is used, you need to update this file, and the README, accordingly. - -### `package.json` - -The provided `package.json` is set up to install all needed packages and config for linting, testing, and semantic-release. You'll need to update the `name`, `description`, and add any new dependencies your plugin files use. - -### `index.js` - -The entrypoint file for your npm package, will most likely just export your plugin registration from the `src` folder. ## Developer Certificate of Origin We use the [Developer Certificate of Origin (DCO)](https://developercertificate.org/) in lieu of a Contributor License Agreement for all contributions to Reaction Commerce open source projects. We request that contributors agree to the terms of the DCO and indicate that agreement by signing all commits made to Reaction Commerce projects by adding a line with your name and email address to every Git commit message contributed: diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/package.json b/packages/api-plugin-fulfillment-method-shipping-ups/package.json index 2c969350ce6..0222d88a11d 100644 --- a/packages/api-plugin-fulfillment-method-shipping-ups/package.json +++ b/packages/api-plugin-fulfillment-method-shipping-ups/package.json @@ -1,7 +1,7 @@ { - "name": "@reactioncommerce/api-plugin-fulfillment-method-shipping-ups", - "description": "Implements Shipping-UPS as a fulfillment method under the type shipping", - "label": "Fulfillment Method Shipping UPS", + "name": "@reactioncommerce/api-plugin-fulfillment-method-shipping-dynamic-rate", + "description": "Implements Shipping-Dynamic Rate as a fulfillment method under the type shipping", + "label": "Fulfillment Method Shipping Dynamic Rate", "version": "1.0.0", "main": "index.js", "type": "module", @@ -15,7 +15,7 @@ "repository": { "type": "git", "url": "git+https://github.com/reactioncommerce/reaction.git", - "directory": "packages/api-plugin-fulfillment-method-shipping-ups" + "directory": "packages/api-plugin-fulfillment-method-shipping-dynamic-rate" }, "author": { "name": "Mailchimp Open Commerce", diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/getFulfillmentMethodsWithQuotesShippingUPS.js b/packages/api-plugin-fulfillment-method-shipping-ups/src/getFulfillmentMethodsWithQuotesShippingDynamicRate.js similarity index 80% rename from packages/api-plugin-fulfillment-method-shipping-ups/src/getFulfillmentMethodsWithQuotesShippingUPS.js rename to packages/api-plugin-fulfillment-method-shipping-ups/src/getFulfillmentMethodsWithQuotesShippingDynamicRate.js index da77724a709..cb768b106f0 100644 --- a/packages/api-plugin-fulfillment-method-shipping-ups/src/getFulfillmentMethodsWithQuotesShippingUPS.js +++ b/packages/api-plugin-fulfillment-method-shipping-ups/src/getFulfillmentMethodsWithQuotesShippingDynamicRate.js @@ -1,8 +1,8 @@ -import calculateUPSRate from "./util/calculateUPSRate.js"; +import calculateDynamicRate from "./util/calculateDynamicRate.js"; -const packageName = "fulfillment-method-shipping-ups"; +const packageName = "fulfillment-method-shipping-dynamic-rate"; const fulfillmentTypeName = "shipping"; -const fulfillmentMethodName = "ups"; +const fulfillmentMethodName = "dynamicRate"; /** * @summary Returns a list of fulfillment method quotes based on the items in a fulfillment group. @@ -17,9 +17,8 @@ const fulfillmentMethodName = "ups"; * shipping rates. * @private */ -export default async function getFulfillmentMethodsWithQuotesShippingUPS(context, commonOrder, previousQueryResults = []) { - const { collections } = context; - const { Fulfillment } = collections; +export default async function getFulfillmentMethodsWithQuotesShippingDynamicRate(context, commonOrder, previousQueryResults = []) { + const { collections: { Fulfillment } } = context; const [rates = [], retrialTargets = []] = previousQueryResults; const currentMethodInfo = { packageName }; @@ -40,11 +39,11 @@ export default async function getFulfillmentMethodsWithQuotesShippingUPS(context } const initialNumOfRates = rates.length; - const awaitedShippingRateDocs = shippingRateDocs.map(async (doc) => { + shippingRateDocs.map(async (doc) => { const carrier = doc.provider.label; const currentPluginMethods = doc.methods.filter((method) => ((method.fulfillmentMethod === (fulfillmentMethodName)) && (method.enabled))); for (const method of currentPluginMethods) { - const updatedMethod = calculateUPSRate(method, commonOrder); + const updatedMethod = calculateDynamicRate(method, commonOrder); rates.push({ carrier, handlingPrice: updatedMethod.handling, @@ -55,13 +54,12 @@ export default async function getFulfillmentMethodsWithQuotesShippingUPS(context }); } }); - await Promise.all(awaitedShippingRateDocs); if (rates.length === initialNumOfRates) { const errorDetails = { requestStatus: "error", shippingProvider: packageName, - message: "UPS shipping did not return any shipping methods." + message: "Dynamic Rate shipping did not return any shipping methods." }; rates.push(errorDetails); retrialTargets.push(currentMethodInfo); diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/getFulfillmentMethodsWithQuotesShippingUPS.test.js b/packages/api-plugin-fulfillment-method-shipping-ups/src/getFulfillmentMethodsWithQuotesShippingDynamicRate.test.js similarity index 73% rename from packages/api-plugin-fulfillment-method-shipping-ups/src/getFulfillmentMethodsWithQuotesShippingUPS.test.js rename to packages/api-plugin-fulfillment-method-shipping-ups/src/getFulfillmentMethodsWithQuotesShippingDynamicRate.test.js index 86162252b17..79e174db36b 100644 --- a/packages/api-plugin-fulfillment-method-shipping-ups/src/getFulfillmentMethodsWithQuotesShippingUPS.test.js +++ b/packages/api-plugin-fulfillment-method-shipping-ups/src/getFulfillmentMethodsWithQuotesShippingDynamicRate.test.js @@ -1,5 +1,5 @@ import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; -import getFulfillmentMethodsWithQuotesShippingUPS from "./getFulfillmentMethodsWithQuotesShippingUPS.js"; +import getFulfillmentMethodsWithQuotesShippingDynamicRate from "./getFulfillmentMethodsWithQuotesShippingDynamicRate.js"; test("should return previousResults if Shipping is not among FailedRequests", async () => { const commonOrder = { @@ -12,7 +12,7 @@ test("should return previousResults if Shipping is not among FailedRequests", as } ] ]; - const result = await getFulfillmentMethodsWithQuotesShippingUPS(mockContext, commonOrder, previousResults); + const result = await getFulfillmentMethodsWithQuotesShippingDynamicRate(mockContext, commonOrder, previousResults); expect(result).toEqual(previousResults); }); @@ -36,7 +36,7 @@ test("should return previousResults if not fulfillment records enabled", async ( mockContext.collections.Fulfillment = { find: jest.fn(() => ({ toArray: () => [] })) }; - const result = await getFulfillmentMethodsWithQuotesShippingUPS(mockContext, commonOrder, previousResults); + const result = await getFulfillmentMethodsWithQuotesShippingDynamicRate(mockContext, commonOrder, previousResults); expect(result).toEqual(previousResults); }); @@ -74,9 +74,9 @@ test("should return rates witout error", async () => { fulfillmentTypes: ["shipping"], group: "Ground", enabled: true, - label: "UPS", - name: "ups", - fulfillmentMethod: "ups", + label: "Dynamic Rate", + name: "dynamicRate", + fulfillmentMethod: "dynamicRate", displayMessageMethod: "Sample display message" }] }; @@ -93,14 +93,14 @@ test("should return rates witout error", async () => { ], group: "Ground", enabled: true, - label: "UPS", - name: "ups", - fulfillmentMethod: "ups", + label: "Dynamic Rate", + name: "dynamicRate", + fulfillmentMethod: "dynamicRate", displayMessageMethod: "Sample display message", - carrier: "UPS", + carrier: "DynamicRate", methodAdditionalData: { - gqlType: "upsData", - upsData: "This is additional STRING data from Shipping - UPS" + gqlType: "dynamicRateData", + dynamicRateData: "This is additional STRING data from Shipping - DynamicRate" } }, rate: 10, @@ -112,6 +112,6 @@ test("should return rates witout error", async () => { mockContext.collections.Fulfillment = { find: jest.fn(() => ({ toArray: () => [shippingDoc] })) }; - const result = await getFulfillmentMethodsWithQuotesShippingUPS(mockContext, commonOrder, previousResults); + const result = await getFulfillmentMethodsWithQuotesShippingDynamicRate(mockContext, commonOrder, previousResults); expect(result).toEqual(expectedResult); }); diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/index.js b/packages/api-plugin-fulfillment-method-shipping-ups/src/index.js index 044a88d135f..b4848d00912 100644 --- a/packages/api-plugin-fulfillment-method-shipping-ups/src/index.js +++ b/packages/api-plugin-fulfillment-method-shipping-ups/src/index.js @@ -1,10 +1,10 @@ import { createRequire } from "module"; -import { MethodUPSData } from "./simpleSchemas.js"; +import { MethodDynamicRateData } from "./simpleSchemas.js"; import preStartup from "./preStartup.js"; import startup from "./startup.js"; import schemas from "./schemas/index.js"; -import getFulfillmentMethodsWithQuotesShippingUPS from "./getFulfillmentMethodsWithQuotesShippingUPS.js"; -import validateOrderMethodsups from "./util/validateOrderMethodsups.js"; +import getFulfillmentMethodsWithQuotesShippingDynamicRate from "./getFulfillmentMethodsWithQuotesShippingDynamicRate.js"; +import validateOrderMethodsDynamicRate from "./util/validateOrderMethodsDynamicRate.js"; const require = createRequire(import.meta.url); const pkg = require("../package.json"); @@ -16,21 +16,20 @@ const pkg = require("../package.json"); */ export default async function register(app) { await app.registerPlugin({ - label: "Fulfillment Method Shipping UPS", - name: "fulfillment-method-shipping-ups", + label: "Fulfillment Method Shipping Dynamic Rate", + name: "fulfillment-method-shipping-dynamic-rate", version: pkg.version, graphQL: { schemas }, simpleSchemas: { - MethodUPSData + MethodDynamicRateData }, functionsByType: { preStartup: [preStartup], startup: [startup], - validateOrderMethods: [validateOrderMethodsups], - getFulfillmentMethodsWithQuotes: [getFulfillmentMethodsWithQuotesShippingUPS], - getFulfillmentMethodsWithQuotesShipping: [getFulfillmentMethodsWithQuotesShippingUPS] + validateOrderMethods: [{ key: "dynamicRate", handler: validateOrderMethodsDynamicRate }], + getFulfillmentMethodsWithQuotes: [{ key: "shipping", handler: getFulfillmentMethodsWithQuotesShippingDynamicRate }] } }); } diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/preStartup.js b/packages/api-plugin-fulfillment-method-shipping-ups/src/preStartup.js index 109faea35d7..12af9c64b04 100644 --- a/packages/api-plugin-fulfillment-method-shipping-ups/src/preStartup.js +++ b/packages/api-plugin-fulfillment-method-shipping-ups/src/preStartup.js @@ -1,11 +1,11 @@ -import { MethodUPSData } from "./simpleSchemas.js"; +import { MethodDynamicRateData } from "./simpleSchemas.js"; /** * @summary Called on preStartup to extend schemas * @param {Object} context Startup context * @returns {undefined} */ -export default async function fulfillmentMethodShippingUPSPreStartup(context) { +export default async function fulfillmentMethodShippingDynamicRatePreStartup(context) { const { simpleSchemas: { ShippingMethod, SelectedFulfillmentOption } } = context; ShippingMethod.extend({ @@ -13,7 +13,7 @@ export default async function fulfillmentMethodShippingUPSPreStartup(context) { type: ShippingMethod.getDefinition( "methodAdditionalData", ["type"] - ).type[0].type.extend(MethodUPSData) + ).type[0].type.extend(MethodDynamicRateData) } }); @@ -22,7 +22,7 @@ export default async function fulfillmentMethodShippingUPSPreStartup(context) { type: SelectedFulfillmentOption.getDefinition( "methodAdditionalData", ["type"] - ).type[0].type.extend(MethodUPSData) + ).type[0].type.extend(MethodDynamicRateData) } }); } diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/schemas/schema.graphql b/packages/api-plugin-fulfillment-method-shipping-ups/src/schemas/schema.graphql index bab8feb5c93..eca0165bc59 100644 --- a/packages/api-plugin-fulfillment-method-shipping-ups/src/schemas/schema.graphql +++ b/packages/api-plugin-fulfillment-method-shipping-ups/src/schemas/schema.graphql @@ -1,7 +1,7 @@ -"Additional data from Shipping UPS" -type upsData { +"Additional data from Shipping Dynamic Rate" +type dynamicRateData { gqlType: String - upsData: String + dynamicRateData: String } -extend union AdditionalData = upsData +extend union AdditionalData = dynamicRateData diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/simpleSchemas.js b/packages/api-plugin-fulfillment-method-shipping-ups/src/simpleSchemas.js index 022235d6ca0..abcce86d622 100644 --- a/packages/api-plugin-fulfillment-method-shipping-ups/src/simpleSchemas.js +++ b/packages/api-plugin-fulfillment-method-shipping-ups/src/simpleSchemas.js @@ -1,16 +1,16 @@ import SimpleSchema from "simpl-schema"; /** - * @name MethodUPSData + * @name MethodDynamicRateData * @memberof Schemas * @type {SimpleSchema} - * @summary Defines UPS additional data + * @summary Defines Dynamic Rate additional data * @property {String} gqlType Defines the method type - * @property {String} upsData UPS Data fields + * @property {String} dynamicRateData Dynamic Rate Data fields */ -export const MethodUPSData = new SimpleSchema({ +export const MethodDynamicRateData = new SimpleSchema({ gqlType: String, - upsData: { + dynamicRateData: { type: String, optional: true } diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/startup.js b/packages/api-plugin-fulfillment-method-shipping-ups/src/startup.js index bd625d8a227..6cfe0fc619e 100644 --- a/packages/api-plugin-fulfillment-method-shipping-ups/src/startup.js +++ b/packages/api-plugin-fulfillment-method-shipping-ups/src/startup.js @@ -7,7 +7,7 @@ import checkAndCreateFulfillmentMethod from "./util/checkAndCreateFulfillmentMet * @param {Object} context.collections Map of MongoDB collections * @returns {undefined} */ -export default async function fulfillmentMethodShippingUPSStartup(context) { +export default async function fulfillmentMethodShippingDynamicRateStartup(context) { context.appEvents.on("afterShopCreate", async (payload) => { const { shop } = payload; const shopId = shop._id; diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/util/__snapshots__/checkAndCreateFulfillmentMethod.test.js.snap b/packages/api-plugin-fulfillment-method-shipping-ups/src/util/__snapshots__/checkAndCreateFulfillmentMethod.test.js.snap index 9db0a1d7407..7205e12df65 100644 --- a/packages/api-plugin-fulfillment-method-shipping-ups/src/util/__snapshots__/checkAndCreateFulfillmentMethod.test.js.snap +++ b/packages/api-plugin-fulfillment-method-shipping-ups/src/util/__snapshots__/checkAndCreateFulfillmentMethod.test.js.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`should throw error and NOT call createFulfillmentMethod mutation 1`] = `"Unable to create fulfillment method Shipping-UPS without defined type"`; +exports[`should throw error and NOT call createFulfillmentMethod mutation 1`] = `"Unable to create fulfillment method Shipping-DynamicRate without defined type"`; diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/util/calculateUPSRate.js b/packages/api-plugin-fulfillment-method-shipping-ups/src/util/calculateDynamicRate.js similarity index 78% rename from packages/api-plugin-fulfillment-method-shipping-ups/src/util/calculateUPSRate.js rename to packages/api-plugin-fulfillment-method-shipping-ups/src/util/calculateDynamicRate.js index 76bc9e808a5..847d82eb7e4 100644 --- a/packages/api-plugin-fulfillment-method-shipping-ups/src/util/calculateUPSRate.js +++ b/packages/api-plugin-fulfillment-method-shipping-ups/src/util/calculateDynamicRate.js @@ -3,11 +3,11 @@ * @param {Object} currentOrder - Current order which provide required details to perform rate calculation * @returns {Object} methodData - additional data */ -function getUPSData() { +function getDynamicRateData() { // currentOrder details could be passed in here and used as input to obtain any external data return { - gqlType: "upsData", - upsData: "This is additional STRING data from Shipping - UPS" + gqlType: "dynamicRateData", + dynamicRateData: "This is additional STRING data from Shipping - DynamicRate" }; } /** @@ -16,7 +16,7 @@ function getUPSData() { * @param {Object} currentOrder - Current order which provide required details to perform rate calculation * @returns {Object} updatedMethod - with the rate details populated */ -export default function calculateUPSRate(method, currentOrder) { +export default function calculateDynamicRate(method, currentOrder) { // Collect order specific details for calculating the rates // const { items, shippingAddress } = currentOrder; @@ -26,8 +26,8 @@ export default function calculateUPSRate(method, currentOrder) { const updatedMethod = method; updatedMethod.rate = 10; updatedMethod.handling = 20; - updatedMethod.carrier = "UPS"; - updatedMethod.methodAdditionalData = getUPSData(currentOrder); + updatedMethod.carrier = "DynamicRate"; + updatedMethod.methodAdditionalData = getDynamicRateData(currentOrder); return updatedMethod; } diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/util/checkAndCreateFulfillmentMethod.js b/packages/api-plugin-fulfillment-method-shipping-ups/src/util/checkAndCreateFulfillmentMethod.js index 83b95570ec0..1ff8453ae6c 100644 --- a/packages/api-plugin-fulfillment-method-shipping-ups/src/util/checkAndCreateFulfillmentMethod.js +++ b/packages/api-plugin-fulfillment-method-shipping-ups/src/util/checkAndCreateFulfillmentMethod.js @@ -6,23 +6,22 @@ import ReactionError from "@reactioncommerce/reaction-error"; * @returns {Boolean} true if entry exist or insert success else false */ export default async function checkAndCreateFulfillmentMethod(context, shopId) { - const { collections } = context; - const { Fulfillment } = collections; + const { collections: { Fulfillment } } = context; const shippingRecord = await Fulfillment.findOne({ fulfillmentType: "shipping", shopId }); - if (!shippingRecord) throw new ReactionError("server-error", "Unable to create fulfillment method Shipping-UPS without defined type"); + if (!shippingRecord) throw new ReactionError("server-error", "Unable to create fulfillment method Shipping-DynamicRate without defined type"); const fulfillmentTypeId = shippingRecord._id; const method = { - name: "ups", - label: "Shipping via UPS", + name: "dynamicRate", + label: "Shipping using DynamicRate", fulfillmentTypes: ["shipping"], group: "Ground", cost: 0, handling: 0, rate: 0, enabled: true, - fulfillmentMethod: "ups", + fulfillmentMethod: "dynamicRate", displayMessageMethod: "Placeholder for display message" }; diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/util/checkAndCreateFulfillmentMethod.test.js b/packages/api-plugin-fulfillment-method-shipping-ups/src/util/checkAndCreateFulfillmentMethod.test.js index 33916db229b..e6faaf0c17f 100644 --- a/packages/api-plugin-fulfillment-method-shipping-ups/src/util/checkAndCreateFulfillmentMethod.test.js +++ b/packages/api-plugin-fulfillment-method-shipping-ups/src/util/checkAndCreateFulfillmentMethod.test.js @@ -18,15 +18,15 @@ test("should call createFulfillmentMethod mutation", async () => { fulfillmentType: "shipping" }; const method = { - name: "ups", - label: "Shipping via UPS", + name: "dynamicRate", + label: "Shipping using DynamicRate", fulfillmentTypes: ["shipping"], group: "Ground", cost: 0, handling: 0, rate: 0, enabled: true, - fulfillmentMethod: "ups", + fulfillmentMethod: "dynamicRate", displayMessageMethod: "Placeholder for display message" }; @@ -55,15 +55,15 @@ test("should throw error and NOT call createFulfillmentMethod mutation", async ( const shopId = "SHOP_ID"; const fulfillmentTypeId = fulfillment._id; const method = { - name: "ups", - label: "Shipping via UPS", + name: "dynamicRate", + label: "Shipping using DynamicRate", fulfillmentTypes: ["shipping"], group: "Ground", cost: 0, handling: 0, rate: 0, enabled: true, - fulfillmentMethod: "ups", + fulfillmentMethod: "dynamicRate", displayMessageMethod: "Placeholder for display message" }; diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/util/validateOrderMethodsDynamicRate.js b/packages/api-plugin-fulfillment-method-shipping-ups/src/util/validateOrderMethodsDynamicRate.js new file mode 100644 index 00000000000..94b6ada9b34 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-ups/src/util/validateOrderMethodsDynamicRate.js @@ -0,0 +1,21 @@ +/** + * @summary Sample dummy function validating the data requirements for this metod + * @param {Object} context - Context object + * @param {Object} commonOrder - Current order which provide available details to perform validation + * @param {Object[]} validationResults - Validation results collected till now + * @returns {Object[]} validationResults - with the validation details populated + */ +export default function validateOrderMethodsDynamicRate(context, commonOrder, validationResults = []) { + // This is for demo only. If this dummy result is pushed, placeOrder will fail + // const validationResult = { + // errorName: "invalid", + // errorType: "ReactionError", + // errorField: "DynamicRate - some field", + // fieldValue: "field-value", + // errorMessage: "Customer address not available to decide shipping address" + // }; + + // validationResults.push(validationResult); + + return validationResults; +} diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/util/validateOrderMethodsups.js b/packages/api-plugin-fulfillment-method-shipping-ups/src/util/validateOrderMethodsups.js deleted file mode 100644 index eb9a0286fa1..00000000000 --- a/packages/api-plugin-fulfillment-method-shipping-ups/src/util/validateOrderMethodsups.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * @summary Sample dummy function validating the data requirements for this metod - * @param {Object} context - Context object - * @param {Object} commonOrder - Current order which provide available details to perform validation - * @param {Object[]} validationResults - Validation results collected till now - * @returns {Object[]} validationResults - with the validation details populated - */ -export default function validateOrderMethodsups(context, commonOrder, validationResults = []) { - // const { items, shippingAddress } = commonOrder; - - const validationResult = { - errorName: "invalid", - errorType: "ReactionError", - errorField: "UPS - some field", - fieldValue: "field-value", - errorMessage: "Customer address not available to decide shipping address" - }; - - validationResults.push(validationResult); - - return validationResults; -} diff --git a/packages/api-plugin-fulfillment-type-shipping/.gitignore b/packages/api-plugin-fulfillment-type-shipping/.gitignore deleted file mode 100644 index ad46b30886f..00000000000 --- a/packages/api-plugin-fulfillment-type-shipping/.gitignore +++ /dev/null @@ -1,61 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# TypeScript v1 declaration files -typings/ - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file -.env - -# next.js build output -.next diff --git a/packages/api-plugin-fulfillment-type-shipping/README.md b/packages/api-plugin-fulfillment-type-shipping/README.md index 863277b6826..1bcd0c1a35b 100644 --- a/packages/api-plugin-fulfillment-type-shipping/README.md +++ b/packages/api-plugin-fulfillment-type-shipping/README.md @@ -1,44 +1,12 @@ # api-plugin-fulfillment-type-shipping -[![npm (scoped)](https://img.shields.io/npm/v/@reactioncommerce/api-plugin-fulfillment-type-shipping.svg)](https://www.npmjs.com/package/@reactioncommerce/api-plugin-fulfillment-type-shipping) -[![CircleCI](https://circleci.com/gh/reactioncommerce/api-plugin-fulfillment-type-shipping.svg?style=svg)](https://circleci.com/gh/reactioncommerce/api-plugin-fulfillment-type-shipping) +This is a fulfillment-type plugin which which works along with the base `api-plugin-fulfillment` and other fulfillment-method plugins (like flat-rate-shipping, dynamic-rate-shipping etc). This plugin provides the basic updates needed to establish the fulfillment-type. +Each of the newly introduced fulfillment-method plugins under this fulfillment-type (shipping) would need to bee implemented as separate plugins. -## Summary - -This plugin implements 'shipping' as one of the fulfillment type. Fulfillment methods under 'shipping' is implemented via plugins like api-plugin-fulfillment-method-shipping-flat-rate and api-plugin-fulfillment-method-shipping-ups. - -The `Developer Certificate of Origin` and `License` sections can stay as they are, assuming `Apache 2` license is used (our preferred license). All other sections of this README should be updated to reflect your plugin. - -## Included in this fulfillment-type-shipping plugin - -### `src/` - -The `src` folder is where you'll put all the plugin files. - -### `.gitignore` - -A basic `gitignore` file - -### `babel.config.cjs` - -If your plugin includes linting and tests, this file is required to allow esmodules to run correctly. - -### `jest.config.cjs` - -If your plugin includes tests, this file is required to allow esmodules to run correctly. You'll need to update the `transformIgnorePatterns` and `moduleNameMapper` sections to include any esmodule `npm` packages used in your plugin. - -### `License.md` - -If your plugin uses `Apache 2` licensing, you can leave this file as-is. If another type of licensing is used, you need to update this file, and the README, accordingly. - -### `package.json` - -The provided `package.json` is set up to install all needed packages and config for linting, testing, and semantic-release. You'll need to update the `name`, `description`, and add any new dependencies your plugin files use. - -### `index.js` - -The entrypoint file for your npm package, will most likely just export your plugin registration from the `src` folder. +This plugins does the following: +* Registers the 'shipping' as a ff-type via registeredFulfillmentTypes: ["shipping"] +* Inserts the default entry for shipping ff-type in Fulfillment collection ## Developer Certificate of Origin We use the [Developer Certificate of Origin (DCO)](https://developercertificate.org/) in lieu of a Contributor License Agreement for all contributions to Reaction Commerce open source projects. We request that contributors agree to the terms of the DCO and indicate that agreement by signing all commits made to Reaction Commerce projects by adding a line with your name and email address to every Git commit message contributed: diff --git a/packages/api-plugin-fulfillment-type-shipping/src/checkAndCreateFulfillmentType.js b/packages/api-plugin-fulfillment-type-shipping/src/checkAndCreateFulfillmentType.js index d5db578a9f1..fdee2d4ff7b 100644 --- a/packages/api-plugin-fulfillment-type-shipping/src/checkAndCreateFulfillmentType.js +++ b/packages/api-plugin-fulfillment-type-shipping/src/checkAndCreateFulfillmentType.js @@ -6,8 +6,7 @@ * @returns {Boolean} true if entry exist or insert success else false */ export default async function checkAndCreateFulfillmentType(context, shopId) { - const { collections } = context; - const { Fulfillment } = collections; + const { collections: { Fulfillment } } = context; const shippingRecord = await Fulfillment.findOne({ fulfillmentType: "shipping", shopId }); if (!shippingRecord) { From 2d9044d1e49e7cfd0255d32f9bd1ecdaa9a1d3be Mon Sep 17 00:00:00 2001 From: Sujith Date: Thu, 3 Nov 2022 17:38:32 +0530 Subject: [PATCH 20/71] fix: picked up the fix #6578 from old plugin Signed-off-by: Sujith --- .../package.json | 1 + .../src/util/attributeDenyCheck.js | 8 +- .../src/util/filterShippingMethods.test.js | 92 ++++++++++++++++++- pnpm-lock.yaml | 74 ++++++++++++++- 4 files changed, 169 insertions(+), 6 deletions(-) diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/package.json b/packages/api-plugin-fulfillment-method-shipping-flat-rate/package.json index d8dbd468daf..56bec6ebf7c 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/package.json +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/package.json @@ -29,6 +29,7 @@ "sideEffects": false, "dependencies": { "simpl-schema": "^1.12.2", + "lodash": "^4.17.15", "@reactioncommerce/logger": "^1.1.3", "@reactioncommerce/reaction-error": "^1.0.1", "@reactioncommerce/random": "~1.0.2", diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/attributeDenyCheck.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/attributeDenyCheck.js index 93be1ca8aea..f0294b698cf 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/attributeDenyCheck.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/attributeDenyCheck.js @@ -1,3 +1,4 @@ +import _ from "lodash"; import operators from "@reactioncommerce/api-utils/operators.js"; import propertyTypes from "@reactioncommerce/api-utils/propertyTypes.js"; import isDestinationRestricted from "./isDestinationRestricted.js"; @@ -25,12 +26,13 @@ export async function attributeDenyCheck(methodRestrictions, method, hydratedOrd // Item must meet all attributes to be restricted return attributes.every((attribute) => { - let attributeFound = operators[attribute.operator](item[attribute.property], propertyTypes[attribute.propertyType](attribute.value)); + const attributePropertyValue = _.get(item, attribute.property); + let attributeFound = operators[attribute.operator](attributePropertyValue, propertyTypes[attribute.propertyType](attribute.value)); // If attribute is an array on the item, use `includes` instead of checking for === // This works for tags, tagIds, and any future attribute that might be an array - if (Array.isArray(item[attribute.property])) { - attributeFound = item[attribute.property].includes(attribute.value); + if (Array.isArray(attributePropertyValue)) { + attributeFound = attributePropertyValue.includes(attribute.value); } if (attributeFound) { diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/filterShippingMethods.test.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/filterShippingMethods.test.js index d95b7433fdf..d4e98a7303b 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/filterShippingMethods.test.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/filterShippingMethods.test.js @@ -52,7 +52,17 @@ const mockHydratedOrderItems = { variantId: "tMkp5QwZog5ihYTfG", weight: 50, width: 10, - tags: [Array] + tags: [Array], + subtotal: { + amount: 12.99, + currencyCode: "USD" + }, + parcel: { + height: 10, + weight: 50, + width: 10, + length: 10 + } }; const mockHydratedOrder = { @@ -854,3 +864,83 @@ test("deny method - multiple attributes - item value is less than $100 AND item expect(allowedMethods).toEqual([]); }); + + +/* +* Tests with nested item/attribute restrictions +*/ +test("deny method - nested attribute - subtotal.amount is less than $100, item restricted", async () => { + const mockMethodRestrictions = [ + { + _id: "allow001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "allow", + destination: { + country: [ + "US" + ] + } + }, + { + _id: "deny001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "deny", + attributes: [ + { + property: "subtotal.amount", + value: 100, + propertyType: "int", + operator: "lt" + } + ] + } + ]; + + mockContext.collections.FlatRateFulfillmentRestrictions.toArray.mockReturnValue(Promise.resolve(mockMethodRestrictions)); + + const allowedMethods = await filterShippingMethods(mockContext, mockShippingMethod, mockHydratedOrder); + + expect(allowedMethods).toEqual([]); +}); + +test("deny method - nested attributes - parcel.weight is greater than 50, no item restrictions", async () => { + const mockMethodRestrictions = [ + { + _id: "allow001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "allow", + destination: { + country: [ + "US" + ] + } + }, + { + _id: "deny001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "deny", + attributes: [ + { + property: "parcel.weight", + value: 50, + propertyType: "int", + operator: "gt" + } + ] + } + ]; + + mockContext.collections.FlatRateFulfillmentRestrictions.toArray.mockReturnValue(Promise.resolve(mockMethodRestrictions)); + + const allowedMethods = await filterShippingMethods(mockContext, mockShippingMethod, mockHydratedOrder); + + expect(allowedMethods).toEqual(mockShippingMethod); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a54914d1c58..673c6bf7b1b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -225,10 +225,10 @@ importers: '@reactioncommerce/api-plugin-payments-example': link:../../packages/api-plugin-payments-example '@reactioncommerce/api-plugin-payments-stripe-sca': link:../../packages/api-plugin-payments-stripe-sca '@reactioncommerce/api-plugin-pricing-simple': link:../../packages/api-plugin-pricing-simple - '@reactioncommerce/api-plugin-products': link:../../packages/api-plugin-products + '@reactioncommerce/api-plugin-products': 1.3.1_graphql@14.7.0 '@reactioncommerce/api-plugin-settings': link:../../packages/api-plugin-settings '@reactioncommerce/api-plugin-shipments': link:../../packages/api-plugin-shipments - '@reactioncommerce/api-plugin-shipments-flat-rate': link:../../packages/api-plugin-shipments-flat-rate + '@reactioncommerce/api-plugin-shipments-flat-rate': 1.0.10_graphql@14.7.0 '@reactioncommerce/api-plugin-shops': link:../../packages/api-plugin-shops '@reactioncommerce/api-plugin-simple-schema': link:../../packages/api-plugin-simple-schema '@reactioncommerce/api-plugin-sitemap-generator': link:../../packages/api-plugin-sitemap-generator @@ -700,12 +700,14 @@ importers: '@reactioncommerce/logger': ^1.1.3 '@reactioncommerce/random': ~1.0.2 '@reactioncommerce/reaction-error': ^1.0.1 + lodash: ^4.17.15 simpl-schema: ^1.12.2 dependencies: '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error + lodash: 4.17.21 simpl-schema: 1.12.3 packages/api-plugin-fulfillment-method-shipping-ups: @@ -4654,6 +4656,54 @@ packages: /@protobufjs/utf8/1.1.0: resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + /@reactioncommerce/api-plugin-products/1.3.1_graphql@14.7.0: + resolution: {integrity: sha512-lnjj9QkRlibsBUOZAgGNi9S13dbZogTouaZ59/+fiIlYJDrSOJ1x4yylGdWMZ6W7K2PvWoZvXk1YrzGopnCflQ==} + engines: {node: '>=14.18.1'} + dependencies: + '@reactioncommerce/api-utils': 1.16.9_graphql@14.7.0 + '@reactioncommerce/logger': 1.1.5 + '@reactioncommerce/random': 1.0.2 + '@reactioncommerce/reaction-error': 1.0.1 + lodash: 4.17.21 + simpl-schema: 1.12.3 + transitivePeerDependencies: + - graphql + dev: false + + /@reactioncommerce/api-plugin-shipments-flat-rate/1.0.10_graphql@14.7.0: + resolution: {integrity: sha512-XilxXESaBQa6v1ChSTduYg8kCTYElI9jy6lWevrfjkRjquSlA/sNZmZOMSkRW0tdZhZMCnAboH+C2r/62IDHBg==} + engines: {node: '>=14.18.1'} + dependencies: + '@reactioncommerce/api-utils': 1.16.9_graphql@14.7.0 + '@reactioncommerce/logger': 1.1.5 + '@reactioncommerce/random': 1.0.2 + '@reactioncommerce/reaction-error': 1.0.1 + lodash: 4.17.21 + simpl-schema: 1.12.3 + transitivePeerDependencies: + - graphql + dev: false + + /@reactioncommerce/api-utils/1.16.9_graphql@14.7.0: + resolution: {integrity: sha512-9jB684K3GISSkkTeiMIniYiFgAtHL1AosSD2GPVpGSpFIUVbEL93e8owEadSIugmJt8yOb3m1+YdThW+kvXlzQ==} + engines: {node: '>=12.14.1'} + dependencies: + '@jest/globals': 26.6.2 + '@reactioncommerce/logger': 1.1.5 + '@reactioncommerce/random': 1.0.2 + '@reactioncommerce/reaction-error': 1.0.1 + accounting-js: 1.1.1 + callsite: 1.0.0 + envalid: 6.0.2 + graphql-fields: 2.0.3 + graphql-relay: 0.9.0_graphql@14.7.0 + lodash: 4.17.21 + ramda: 0.27.2 + transliteration: 2.3.5 + transitivePeerDependencies: + - graphql + dev: false + /@reactioncommerce/babel-remove-es-create-require/1.0.0_@babel+core@7.19.0: resolution: {integrity: sha512-yR1vMj76hK5D9/VcXjFpk2OMoYpQdbvEgGcJ79cEzTL/jkFwIs53Zl6PNBjcC608P8qHl5eypEYRwG7CCGLzwQ==} peerDependencies: @@ -4709,11 +4759,27 @@ packages: eslint-plugin-you-dont-need-lodash-underscore: 6.12.0 dev: true + /@reactioncommerce/logger/1.1.5: + resolution: {integrity: sha512-fG6wohv9NoX6ZjNzs0YcFRFxHyndO4VitMCrbz+8HrnM7LNJtQRzrUcR58RptagYixZ4GyiHY6WhmIfrMpNvLw==} + dependencies: + bunyan: 1.8.15 + bunyan-format: 0.2.1 + node-loggly-bulk: 2.2.5 + dev: false + /@reactioncommerce/nodemailer/5.0.5: resolution: {integrity: sha512-u4ontTETlROmLglkMDyouMXlX62NXOGfOUAd75Ilk3W4tcsRjRXX+g5C5B4mBCCcJB0wHn1yh/a4pOYkn81vUQ==} engines: {node: '>=4.0.0'} dev: false + /@reactioncommerce/random/1.0.2: + resolution: {integrity: sha512-02eARZh6FQ45tEXXZUwnz90XWQasLpnV140CcuOl/L3dhEL4Emu2oCZemVhKvNb2JMmm5vOG2Y0CUb12xAx73w==} + dev: false + + /@reactioncommerce/reaction-error/1.0.1: + resolution: {integrity: sha512-l1HyDTFw4m1j8EPWNA/bbF2GZZaLYQjwmgniSjjM1cgnhfjCqeLywi501oPkmH8E84Jh0EH+4ADhNCcZ4m4gug==} + dev: false + /@sinclair/typebox/0.24.41: resolution: {integrity: sha512-TJCgQurls4FipFvHeC+gfAzb+GGstL0TDwYJKQVtTeSvJIznWzP7g3bAd5gEBlr8+bIxqnWS9VGVWREDhmE8jA==} dev: true @@ -12421,6 +12487,10 @@ packages: engines: {node: '>=8'} dev: false + /ramda/0.27.2: + resolution: {integrity: sha512-SbiLPU40JuJniHexQSAgad32hfwd+DRUdwF2PlVuI5RZD0/vahUco7R8vD86J/tcEKKF9vZrUVwgtmGCqlCKyA==} + dev: false + /ramda/0.28.0: resolution: {integrity: sha512-9QnLuG/kPVgWvMQ4aODhsBUFKOUmnbUnsSXACv+NCQZcHbeb+v8Lodp8OVxtRULN1/xOyYLLaL6npE6dMq5QTA==} dev: false From 37e3dd6cfc5b017ae6c4a79ad05cbe6364c99e47 Mon Sep 17 00:00:00 2001 From: Sujith Date: Fri, 28 Oct 2022 09:23:05 +0530 Subject: [PATCH 21/71] fix: renamed collection in test.js Signed-off-by: Sujith --- .../mutations/createFlatRateFulfillmentRestriction.test.js | 1 - .../src/util/filterShippingMethods.test.js | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/createFlatRateFulfillmentRestriction.test.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/createFlatRateFulfillmentRestriction.test.js index fab5925a998..c1d6e802722 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/createFlatRateFulfillmentRestriction.test.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/createFlatRateFulfillmentRestriction.test.js @@ -8,7 +8,6 @@ mockContext.collections.FulfillmentRestrictions = mockCollection("FulfillmentRes mockContext.validatePermissions.mockReturnValueOnce(Promise.resolve(null)); test("add a flat rate fulfillment restriction", async () => { - // mockContext.collections.FlatRateFulfillmentRestrictions.insertOne.mockReturnValueOnce(Promise.resolve({})); mockContext.collections.FulfillmentRestrictions.insertOne.mockReturnValueOnce(Promise.resolve({})); const result = await createFlatRateFulfillmentRestrictionMutation(mockContext, { diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/filterShippingMethods.test.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/filterShippingMethods.test.js index d4e98a7303b..ab0fc00b9e3 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/filterShippingMethods.test.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/filterShippingMethods.test.js @@ -900,7 +900,7 @@ test("deny method - nested attribute - subtotal.amount is less than $100, item r } ]; - mockContext.collections.FlatRateFulfillmentRestrictions.toArray.mockReturnValue(Promise.resolve(mockMethodRestrictions)); + mockContext.collections.FulfillmentRestrictions.toArray.mockReturnValue(Promise.resolve(mockMethodRestrictions)); const allowedMethods = await filterShippingMethods(mockContext, mockShippingMethod, mockHydratedOrder); @@ -938,7 +938,7 @@ test("deny method - nested attributes - parcel.weight is greater than 50, no ite } ]; - mockContext.collections.FlatRateFulfillmentRestrictions.toArray.mockReturnValue(Promise.resolve(mockMethodRestrictions)); + mockContext.collections.FulfillmentRestrictions.toArray.mockReturnValue(Promise.resolve(mockMethodRestrictions)); const allowedMethods = await filterShippingMethods(mockContext, mockShippingMethod, mockHydratedOrder); From 70bb0b92e2324e82b6f3d70efb8079d92a22bd3c Mon Sep 17 00:00:00 2001 From: Sujith Date: Sat, 29 Oct 2022 11:31:34 +0530 Subject: [PATCH 22/71] fix: import fix Signed-off-by: Sujith --- .../api-plugin-fulfillment-method-shipping-ups/src/startup.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/startup.js b/packages/api-plugin-fulfillment-method-shipping-ups/src/startup.js index 6cfe0fc619e..04ffae4ca1b 100644 --- a/packages/api-plugin-fulfillment-method-shipping-ups/src/startup.js +++ b/packages/api-plugin-fulfillment-method-shipping-ups/src/startup.js @@ -1,5 +1,5 @@ import ReactionError from "@reactioncommerce/reaction-error"; -import checkAndCreateFulfillmentMethod from "./util/checkAndCreateFulfillmentMethod"; +import checkAndCreateFulfillmentMethod from "./util/checkAndCreateFulfillmentMethod.js"; /** * @summary Called on startup to create the root entry of this fulfillment type in Fulfillment collection From 975ea9385fd382e10227a31e18b262e138eb7c27 Mon Sep 17 00:00:00 2001 From: Sujith Date: Sat, 29 Oct 2022 11:40:58 +0530 Subject: [PATCH 23/71] fix: renamed top-level folder ups to dynamic-rate Signed-off-by: Sujith --- .../LICENSE | 0 .../README.md | 0 .../babel.config.cjs | 0 .../index.js | 0 .../jest.config.cjs | 0 .../package.json | 0 .../src/getFulfillmentMethodsWithQuotesShippingDynamicRate.js | 0 .../getFulfillmentMethodsWithQuotesShippingDynamicRate.test.js | 0 .../src/index.js | 0 .../src/preStartup.js | 0 .../src/schemas/index.js | 0 .../src/schemas/schema.graphql | 0 .../src/simpleSchemas.js | 0 .../src/startup.js | 0 .../__snapshots__/checkAndCreateFulfillmentMethod.test.js.snap | 0 .../src/util/calculateDynamicRate.js | 0 .../src/util/checkAndCreateFulfillmentMethod.js | 0 .../src/util/checkAndCreateFulfillmentMethod.test.js | 0 .../src/util/validateOrderMethodsDynamicRate.js | 0 19 files changed, 0 insertions(+), 0 deletions(-) rename packages/{api-plugin-fulfillment-method-shipping-ups => api-plugin-fulfillment-method-shipping-dynamic-rate}/LICENSE (100%) rename packages/{api-plugin-fulfillment-method-shipping-ups => api-plugin-fulfillment-method-shipping-dynamic-rate}/README.md (100%) rename packages/{api-plugin-fulfillment-method-shipping-ups => api-plugin-fulfillment-method-shipping-dynamic-rate}/babel.config.cjs (100%) rename packages/{api-plugin-fulfillment-method-shipping-ups => api-plugin-fulfillment-method-shipping-dynamic-rate}/index.js (100%) rename packages/{api-plugin-fulfillment-method-shipping-ups => api-plugin-fulfillment-method-shipping-dynamic-rate}/jest.config.cjs (100%) rename packages/{api-plugin-fulfillment-method-shipping-ups => api-plugin-fulfillment-method-shipping-dynamic-rate}/package.json (100%) rename packages/{api-plugin-fulfillment-method-shipping-ups => api-plugin-fulfillment-method-shipping-dynamic-rate}/src/getFulfillmentMethodsWithQuotesShippingDynamicRate.js (100%) rename packages/{api-plugin-fulfillment-method-shipping-ups => api-plugin-fulfillment-method-shipping-dynamic-rate}/src/getFulfillmentMethodsWithQuotesShippingDynamicRate.test.js (100%) rename packages/{api-plugin-fulfillment-method-shipping-ups => api-plugin-fulfillment-method-shipping-dynamic-rate}/src/index.js (100%) rename packages/{api-plugin-fulfillment-method-shipping-ups => api-plugin-fulfillment-method-shipping-dynamic-rate}/src/preStartup.js (100%) rename packages/{api-plugin-fulfillment-method-shipping-ups => api-plugin-fulfillment-method-shipping-dynamic-rate}/src/schemas/index.js (100%) rename packages/{api-plugin-fulfillment-method-shipping-ups => api-plugin-fulfillment-method-shipping-dynamic-rate}/src/schemas/schema.graphql (100%) rename packages/{api-plugin-fulfillment-method-shipping-ups => api-plugin-fulfillment-method-shipping-dynamic-rate}/src/simpleSchemas.js (100%) rename packages/{api-plugin-fulfillment-method-shipping-ups => api-plugin-fulfillment-method-shipping-dynamic-rate}/src/startup.js (100%) rename packages/{api-plugin-fulfillment-method-shipping-ups => api-plugin-fulfillment-method-shipping-dynamic-rate}/src/util/__snapshots__/checkAndCreateFulfillmentMethod.test.js.snap (100%) rename packages/{api-plugin-fulfillment-method-shipping-ups => api-plugin-fulfillment-method-shipping-dynamic-rate}/src/util/calculateDynamicRate.js (100%) rename packages/{api-plugin-fulfillment-method-shipping-ups => api-plugin-fulfillment-method-shipping-dynamic-rate}/src/util/checkAndCreateFulfillmentMethod.js (100%) rename packages/{api-plugin-fulfillment-method-shipping-ups => api-plugin-fulfillment-method-shipping-dynamic-rate}/src/util/checkAndCreateFulfillmentMethod.test.js (100%) rename packages/{api-plugin-fulfillment-method-shipping-ups => api-plugin-fulfillment-method-shipping-dynamic-rate}/src/util/validateOrderMethodsDynamicRate.js (100%) diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/LICENSE b/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/LICENSE similarity index 100% rename from packages/api-plugin-fulfillment-method-shipping-ups/LICENSE rename to packages/api-plugin-fulfillment-method-shipping-dynamic-rate/LICENSE diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/README.md b/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/README.md similarity index 100% rename from packages/api-plugin-fulfillment-method-shipping-ups/README.md rename to packages/api-plugin-fulfillment-method-shipping-dynamic-rate/README.md diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/babel.config.cjs b/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/babel.config.cjs similarity index 100% rename from packages/api-plugin-fulfillment-method-shipping-ups/babel.config.cjs rename to packages/api-plugin-fulfillment-method-shipping-dynamic-rate/babel.config.cjs diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/index.js b/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/index.js similarity index 100% rename from packages/api-plugin-fulfillment-method-shipping-ups/index.js rename to packages/api-plugin-fulfillment-method-shipping-dynamic-rate/index.js diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/jest.config.cjs b/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/jest.config.cjs similarity index 100% rename from packages/api-plugin-fulfillment-method-shipping-ups/jest.config.cjs rename to packages/api-plugin-fulfillment-method-shipping-dynamic-rate/jest.config.cjs diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/package.json b/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/package.json similarity index 100% rename from packages/api-plugin-fulfillment-method-shipping-ups/package.json rename to packages/api-plugin-fulfillment-method-shipping-dynamic-rate/package.json diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/getFulfillmentMethodsWithQuotesShippingDynamicRate.js b/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/getFulfillmentMethodsWithQuotesShippingDynamicRate.js similarity index 100% rename from packages/api-plugin-fulfillment-method-shipping-ups/src/getFulfillmentMethodsWithQuotesShippingDynamicRate.js rename to packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/getFulfillmentMethodsWithQuotesShippingDynamicRate.js diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/getFulfillmentMethodsWithQuotesShippingDynamicRate.test.js b/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/getFulfillmentMethodsWithQuotesShippingDynamicRate.test.js similarity index 100% rename from packages/api-plugin-fulfillment-method-shipping-ups/src/getFulfillmentMethodsWithQuotesShippingDynamicRate.test.js rename to packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/getFulfillmentMethodsWithQuotesShippingDynamicRate.test.js diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/index.js b/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/index.js similarity index 100% rename from packages/api-plugin-fulfillment-method-shipping-ups/src/index.js rename to packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/index.js diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/preStartup.js b/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/preStartup.js similarity index 100% rename from packages/api-plugin-fulfillment-method-shipping-ups/src/preStartup.js rename to packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/preStartup.js diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/schemas/index.js b/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/schemas/index.js similarity index 100% rename from packages/api-plugin-fulfillment-method-shipping-ups/src/schemas/index.js rename to packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/schemas/index.js diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/schemas/schema.graphql b/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/schemas/schema.graphql similarity index 100% rename from packages/api-plugin-fulfillment-method-shipping-ups/src/schemas/schema.graphql rename to packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/schemas/schema.graphql diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/simpleSchemas.js b/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/simpleSchemas.js similarity index 100% rename from packages/api-plugin-fulfillment-method-shipping-ups/src/simpleSchemas.js rename to packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/simpleSchemas.js diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/startup.js b/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/startup.js similarity index 100% rename from packages/api-plugin-fulfillment-method-shipping-ups/src/startup.js rename to packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/startup.js diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/util/__snapshots__/checkAndCreateFulfillmentMethod.test.js.snap b/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/util/__snapshots__/checkAndCreateFulfillmentMethod.test.js.snap similarity index 100% rename from packages/api-plugin-fulfillment-method-shipping-ups/src/util/__snapshots__/checkAndCreateFulfillmentMethod.test.js.snap rename to packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/util/__snapshots__/checkAndCreateFulfillmentMethod.test.js.snap diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/util/calculateDynamicRate.js b/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/util/calculateDynamicRate.js similarity index 100% rename from packages/api-plugin-fulfillment-method-shipping-ups/src/util/calculateDynamicRate.js rename to packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/util/calculateDynamicRate.js diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/util/checkAndCreateFulfillmentMethod.js b/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/util/checkAndCreateFulfillmentMethod.js similarity index 100% rename from packages/api-plugin-fulfillment-method-shipping-ups/src/util/checkAndCreateFulfillmentMethod.js rename to packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/util/checkAndCreateFulfillmentMethod.js diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/util/checkAndCreateFulfillmentMethod.test.js b/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/util/checkAndCreateFulfillmentMethod.test.js similarity index 100% rename from packages/api-plugin-fulfillment-method-shipping-ups/src/util/checkAndCreateFulfillmentMethod.test.js rename to packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/util/checkAndCreateFulfillmentMethod.test.js diff --git a/packages/api-plugin-fulfillment-method-shipping-ups/src/util/validateOrderMethodsDynamicRate.js b/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/util/validateOrderMethodsDynamicRate.js similarity index 100% rename from packages/api-plugin-fulfillment-method-shipping-ups/src/util/validateOrderMethodsDynamicRate.js rename to packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/util/validateOrderMethodsDynamicRate.js From 2d543a3e8344bcb4a2a74d964fcb627d2f52af2f Mon Sep 17 00:00:00 2001 From: Sujith Date: Thu, 3 Nov 2022 17:44:34 +0530 Subject: [PATCH 24/71] fix: pnpm-lock without snyk Signed-off-by: Sujith --- pnpm-lock.yaml | 88 ++++++-------------------------------------------- 1 file changed, 10 insertions(+), 78 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 673c6bf7b1b..ed27df4cc88 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -225,10 +225,10 @@ importers: '@reactioncommerce/api-plugin-payments-example': link:../../packages/api-plugin-payments-example '@reactioncommerce/api-plugin-payments-stripe-sca': link:../../packages/api-plugin-payments-stripe-sca '@reactioncommerce/api-plugin-pricing-simple': link:../../packages/api-plugin-pricing-simple - '@reactioncommerce/api-plugin-products': 1.3.1_graphql@14.7.0 + '@reactioncommerce/api-plugin-products': link:../../packages/api-plugin-products '@reactioncommerce/api-plugin-settings': link:../../packages/api-plugin-settings '@reactioncommerce/api-plugin-shipments': link:../../packages/api-plugin-shipments - '@reactioncommerce/api-plugin-shipments-flat-rate': 1.0.10_graphql@14.7.0 + '@reactioncommerce/api-plugin-shipments-flat-rate': link:../../packages/api-plugin-shipments-flat-rate '@reactioncommerce/api-plugin-shops': link:../../packages/api-plugin-shops '@reactioncommerce/api-plugin-simple-schema': link:../../packages/api-plugin-simple-schema '@reactioncommerce/api-plugin-sitemap-generator': link:../../packages/api-plugin-sitemap-generator @@ -694,30 +694,30 @@ importers: '@reactioncommerce/api-plugin-carts': link:../api-plugin-carts '@reactioncommerce/data-factory': 1.0.1 - packages/api-plugin-fulfillment-method-shipping-flat-rate: + packages/api-plugin-fulfillment-method-shipping-dynamic-rate: specifiers: '@reactioncommerce/api-utils': ^1.16.9 - '@reactioncommerce/logger': ^1.1.3 - '@reactioncommerce/random': ~1.0.2 '@reactioncommerce/reaction-error': ^1.0.1 - lodash: ^4.17.15 simpl-schema: ^1.12.2 dependencies: '@reactioncommerce/api-utils': link:../api-utils - '@reactioncommerce/logger': link:../logger - '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error - lodash: 4.17.21 simpl-schema: 1.12.3 - packages/api-plugin-fulfillment-method-shipping-ups: + packages/api-plugin-fulfillment-method-shipping-flat-rate: specifiers: '@reactioncommerce/api-utils': ^1.16.9 + '@reactioncommerce/logger': ^1.1.3 + '@reactioncommerce/random': ~1.0.2 '@reactioncommerce/reaction-error': ^1.0.1 + lodash: ^4.17.15 simpl-schema: ^1.12.2 dependencies: '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/logger': link:../logger + '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error + lodash: 4.17.21 simpl-schema: 1.12.3 packages/api-plugin-fulfillment-type-shipping: @@ -4656,54 +4656,6 @@ packages: /@protobufjs/utf8/1.1.0: resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} - /@reactioncommerce/api-plugin-products/1.3.1_graphql@14.7.0: - resolution: {integrity: sha512-lnjj9QkRlibsBUOZAgGNi9S13dbZogTouaZ59/+fiIlYJDrSOJ1x4yylGdWMZ6W7K2PvWoZvXk1YrzGopnCflQ==} - engines: {node: '>=14.18.1'} - dependencies: - '@reactioncommerce/api-utils': 1.16.9_graphql@14.7.0 - '@reactioncommerce/logger': 1.1.5 - '@reactioncommerce/random': 1.0.2 - '@reactioncommerce/reaction-error': 1.0.1 - lodash: 4.17.21 - simpl-schema: 1.12.3 - transitivePeerDependencies: - - graphql - dev: false - - /@reactioncommerce/api-plugin-shipments-flat-rate/1.0.10_graphql@14.7.0: - resolution: {integrity: sha512-XilxXESaBQa6v1ChSTduYg8kCTYElI9jy6lWevrfjkRjquSlA/sNZmZOMSkRW0tdZhZMCnAboH+C2r/62IDHBg==} - engines: {node: '>=14.18.1'} - dependencies: - '@reactioncommerce/api-utils': 1.16.9_graphql@14.7.0 - '@reactioncommerce/logger': 1.1.5 - '@reactioncommerce/random': 1.0.2 - '@reactioncommerce/reaction-error': 1.0.1 - lodash: 4.17.21 - simpl-schema: 1.12.3 - transitivePeerDependencies: - - graphql - dev: false - - /@reactioncommerce/api-utils/1.16.9_graphql@14.7.0: - resolution: {integrity: sha512-9jB684K3GISSkkTeiMIniYiFgAtHL1AosSD2GPVpGSpFIUVbEL93e8owEadSIugmJt8yOb3m1+YdThW+kvXlzQ==} - engines: {node: '>=12.14.1'} - dependencies: - '@jest/globals': 26.6.2 - '@reactioncommerce/logger': 1.1.5 - '@reactioncommerce/random': 1.0.2 - '@reactioncommerce/reaction-error': 1.0.1 - accounting-js: 1.1.1 - callsite: 1.0.0 - envalid: 6.0.2 - graphql-fields: 2.0.3 - graphql-relay: 0.9.0_graphql@14.7.0 - lodash: 4.17.21 - ramda: 0.27.2 - transliteration: 2.3.5 - transitivePeerDependencies: - - graphql - dev: false - /@reactioncommerce/babel-remove-es-create-require/1.0.0_@babel+core@7.19.0: resolution: {integrity: sha512-yR1vMj76hK5D9/VcXjFpk2OMoYpQdbvEgGcJ79cEzTL/jkFwIs53Zl6PNBjcC608P8qHl5eypEYRwG7CCGLzwQ==} peerDependencies: @@ -4759,27 +4711,11 @@ packages: eslint-plugin-you-dont-need-lodash-underscore: 6.12.0 dev: true - /@reactioncommerce/logger/1.1.5: - resolution: {integrity: sha512-fG6wohv9NoX6ZjNzs0YcFRFxHyndO4VitMCrbz+8HrnM7LNJtQRzrUcR58RptagYixZ4GyiHY6WhmIfrMpNvLw==} - dependencies: - bunyan: 1.8.15 - bunyan-format: 0.2.1 - node-loggly-bulk: 2.2.5 - dev: false - /@reactioncommerce/nodemailer/5.0.5: resolution: {integrity: sha512-u4ontTETlROmLglkMDyouMXlX62NXOGfOUAd75Ilk3W4tcsRjRXX+g5C5B4mBCCcJB0wHn1yh/a4pOYkn81vUQ==} engines: {node: '>=4.0.0'} dev: false - /@reactioncommerce/random/1.0.2: - resolution: {integrity: sha512-02eARZh6FQ45tEXXZUwnz90XWQasLpnV140CcuOl/L3dhEL4Emu2oCZemVhKvNb2JMmm5vOG2Y0CUb12xAx73w==} - dev: false - - /@reactioncommerce/reaction-error/1.0.1: - resolution: {integrity: sha512-l1HyDTFw4m1j8EPWNA/bbF2GZZaLYQjwmgniSjjM1cgnhfjCqeLywi501oPkmH8E84Jh0EH+4ADhNCcZ4m4gug==} - dev: false - /@sinclair/typebox/0.24.41: resolution: {integrity: sha512-TJCgQurls4FipFvHeC+gfAzb+GGstL0TDwYJKQVtTeSvJIznWzP7g3bAd5gEBlr8+bIxqnWS9VGVWREDhmE8jA==} dev: true @@ -12487,10 +12423,6 @@ packages: engines: {node: '>=8'} dev: false - /ramda/0.27.2: - resolution: {integrity: sha512-SbiLPU40JuJniHexQSAgad32hfwd+DRUdwF2PlVuI5RZD0/vahUco7R8vD86J/tcEKKF9vZrUVwgtmGCqlCKyA==} - dev: false - /ramda/0.28.0: resolution: {integrity: sha512-9QnLuG/kPVgWvMQ4aODhsBUFKOUmnbUnsSXACv+NCQZcHbeb+v8Lodp8OVxtRULN1/xOyYLLaL6npE6dMq5QTA==} dev: false From 2da280331b89535e9cfae2328dee1dd659c28add Mon Sep 17 00:00:00 2001 From: Sujith Date: Thu, 20 Oct 2022 17:52:42 +0530 Subject: [PATCH 25/71] feat: changes related to carts plugin Signed-off-by: Sujith --- packages/api-plugin-carts/src/cartVersion.js | 3 + packages/api-plugin-carts/src/index.js | 6 +- .../src/mutations/createCart.js | 2 + .../src/mutations/createCart.test.js | 1 + .../api-plugin-carts/src/mutations/index.js | 2 + .../mutations/setFulfillmentTypeForItems.js | 53 ++++++++ .../src/mutations/transformAndValidateCart.js | 2 +- .../src/resolvers/Mutation/index.js | 2 + .../Mutation/setFulfillmentTypeForItems.js | 36 ++++++ .../api-plugin-carts/src/schemas/cart.graphql | 47 +++++++ .../api-plugin-carts/src/simpleSchemas.js | 38 +++++- .../api-plugin-carts/src/util/addCartItems.js | 8 +- .../src/util/updateCartFulfillmentGroups.js | 121 +++++++++++++----- .../src/util/xformCartGroupToCommonOrder.js | 3 + .../src/xforms/xformCartCheckout.js | 4 +- 15 files changed, 289 insertions(+), 39 deletions(-) create mode 100644 packages/api-plugin-carts/src/cartVersion.js create mode 100644 packages/api-plugin-carts/src/mutations/setFulfillmentTypeForItems.js create mode 100644 packages/api-plugin-carts/src/resolvers/Mutation/setFulfillmentTypeForItems.js diff --git a/packages/api-plugin-carts/src/cartVersion.js b/packages/api-plugin-carts/src/cartVersion.js new file mode 100644 index 00000000000..b0cd7bdcbe3 --- /dev/null +++ b/packages/api-plugin-carts/src/cartVersion.js @@ -0,0 +1,3 @@ +// Update this for each major change of cart object +// version 2 => Fulfillment changes +export const cartVersion = 2; diff --git a/packages/api-plugin-carts/src/index.js b/packages/api-plugin-carts/src/index.js index ad2064899d7..062ea6e4b8a 100644 --- a/packages/api-plugin-carts/src/index.js +++ b/packages/api-plugin-carts/src/index.js @@ -5,7 +5,7 @@ import queries from "./queries/index.js"; import resolvers from "./resolvers/index.js"; import schemas from "./schemas/index.js"; import { registerPluginHandlerForCart } from "./registration.js"; -import { Cart, CartItem } from "./simpleSchemas.js"; +import { Cart, CartItem, Shipment, ShippingMethod } from "./simpleSchemas.js"; import startup from "./startup.js"; /** @@ -59,7 +59,9 @@ export default async function register(app) { policies, simpleSchemas: { Cart, - CartItem + CartItem, + Shipment, + ShippingMethod } }); } diff --git a/packages/api-plugin-carts/src/mutations/createCart.js b/packages/api-plugin-carts/src/mutations/createCart.js index e7a66dfdecc..18e116d8354 100644 --- a/packages/api-plugin-carts/src/mutations/createCart.js +++ b/packages/api-plugin-carts/src/mutations/createCart.js @@ -2,6 +2,7 @@ import hashToken from "@reactioncommerce/api-utils/hashToken.js"; import Random from "@reactioncommerce/random"; import ReactionError from "@reactioncommerce/reaction-error"; import Logger from "@reactioncommerce/logger"; +import { cartVersion } from "../cartVersion.js"; import addCartItems from "../util/addCartItems.js"; /** @@ -61,6 +62,7 @@ export default async function createCart(context, input) { const createdAt = new Date(); const newCart = { _id: Random.id(), + cartVersion, accountId, anonymousAccessToken: anonymousAccessToken && hashToken(anonymousAccessToken), currencyCode: cartCurrencyCode, diff --git a/packages/api-plugin-carts/src/mutations/createCart.test.js b/packages/api-plugin-carts/src/mutations/createCart.test.js index 3c4525dc805..3dc80e46f52 100644 --- a/packages/api-plugin-carts/src/mutations/createCart.test.js +++ b/packages/api-plugin-carts/src/mutations/createCart.test.js @@ -84,6 +84,7 @@ test("creates an anonymous cart if no user is logged in", async () => { expect(result).toEqual({ cart: { _id: jasmine.any(String), + cartVersion: 2, accountId: null, anonymousAccessToken: jasmine.any(String), currencyCode: "USD", diff --git a/packages/api-plugin-carts/src/mutations/index.js b/packages/api-plugin-carts/src/mutations/index.js index 373a9b93517..684394f7744 100644 --- a/packages/api-plugin-carts/src/mutations/index.js +++ b/packages/api-plugin-carts/src/mutations/index.js @@ -11,6 +11,7 @@ import saveCart from "./saveCart.js"; import saveManyCarts from "./saveManyCarts.js"; import setEmailOnAnonymousCart from "./setEmailOnAnonymousCart.js"; import setShippingAddressOnCart from "./setShippingAddressOnCart.js"; +import setFulfillmentTypeForItems from "./setFulfillmentTypeForItems.js"; import transformAndValidateCart from "./transformAndValidateCart.js"; import updateCartItemsQuantity from "./updateCartItemsQuantity.js"; @@ -28,6 +29,7 @@ export default { saveManyCarts, setEmailOnAnonymousCart, setShippingAddressOnCart, + setFulfillmentTypeForItems, transformAndValidateCart, updateCartItemsQuantity }; diff --git a/packages/api-plugin-carts/src/mutations/setFulfillmentTypeForItems.js b/packages/api-plugin-carts/src/mutations/setFulfillmentTypeForItems.js new file mode 100644 index 00000000000..612ad2c963f --- /dev/null +++ b/packages/api-plugin-carts/src/mutations/setFulfillmentTypeForItems.js @@ -0,0 +1,53 @@ +import SimpleSchema from "simpl-schema"; +import hashToken from "@reactioncommerce/api-utils/hashToken.js"; +import ReactionError from "@reactioncommerce/reaction-error"; + +const inputSchema = new SimpleSchema({ + "cartId": String, + "cartToken": String, + "fulfillmentType": String, + "items": { + type: Array + }, + "items.$": String +}); + +/** + * @method setFulfillmentTypeForItems + * @summary Assigns the selected fulfillment group to the items provided + * @param {Object} context - an object containing the per-request state + * @param {Object} input - an object of all mutation arguments that were sent by the client + * @param {String} input.cartId - An anonymous cart ID + * @param {String} input.cartToken - The cartToken for accessing the anonymous cart + * @param {String} input.fulfillmentType - The Fulfillment type to be set for the provided items + * @param {String[]} input.items - The array of items to be requested to be assigned to the selected fulfillment type + * @returns {Promise} An object with `cart` property containing the updated cart + */ +export default async function setFulfillmentTypeForItems(context, input) { + inputSchema.validate(input || {}); + + const { collections: { Cart } } = context; + const { cartId, cartToken, fulfillmentType, items: itemsInput } = input; + + const cart = await Cart.findOne({ + _id: cartId, + anonymousAccessToken: hashToken(cartToken) + }); + + if (!cart) throw new ReactionError("not-found", "Cart not found"); + + if (!fulfillmentType || fulfillmentType === "undecided") throw new ReactionError("invalid-param", "Invalid Fulfillment Type received"); + + if (!itemsInput || itemsInput.length === 0) throw new ReactionError("invalid-param", "Items not provided"); + + cart.items = (cart.items || []).map((item) => { + if (itemsInput.includes(item._id)) { + item.selectedFulfillmentType = fulfillmentType; + } + return item; + }); + + const savedCart = await context.mutations.saveCart(context, cart); + + return { cart: savedCart }; +} diff --git a/packages/api-plugin-carts/src/mutations/transformAndValidateCart.js b/packages/api-plugin-carts/src/mutations/transformAndValidateCart.js index 544ea4ce836..eefa2a12528 100644 --- a/packages/api-plugin-carts/src/mutations/transformAndValidateCart.js +++ b/packages/api-plugin-carts/src/mutations/transformAndValidateCart.js @@ -15,7 +15,7 @@ const logCtx = { name: "cart", file: "transformAndValidateCart" }; */ export default async function transformAndValidateCart(context, cart) { const { simpleSchemas: { Cart: cartSchema } } = context; - updateCartFulfillmentGroups(context, cart); + await updateCartFulfillmentGroups(context, cart); let commonOrders; diff --git a/packages/api-plugin-carts/src/resolvers/Mutation/index.js b/packages/api-plugin-carts/src/resolvers/Mutation/index.js index 2f6e17c46b8..fd37bbef740 100644 --- a/packages/api-plugin-carts/src/resolvers/Mutation/index.js +++ b/packages/api-plugin-carts/src/resolvers/Mutation/index.js @@ -4,6 +4,7 @@ import reconcileCarts from "./reconcileCarts.js"; import removeCartItems from "./removeCartItems.js"; import setEmailOnAnonymousCart from "./setEmailOnAnonymousCart.js"; import setShippingAddressOnCart from "./setShippingAddressOnCart.js"; +import setFulfillmentTypeForItems from "./setFulfillmentTypeForItems.js"; import updateCartItemsQuantity from "./updateCartItemsQuantity.js"; export default { @@ -13,5 +14,6 @@ export default { removeCartItems, setEmailOnAnonymousCart, setShippingAddressOnCart, + setFulfillmentTypeForItems, updateCartItemsQuantity }; diff --git a/packages/api-plugin-carts/src/resolvers/Mutation/setFulfillmentTypeForItems.js b/packages/api-plugin-carts/src/resolvers/Mutation/setFulfillmentTypeForItems.js new file mode 100644 index 00000000000..dc7daa5718d --- /dev/null +++ b/packages/api-plugin-carts/src/resolvers/Mutation/setFulfillmentTypeForItems.js @@ -0,0 +1,36 @@ +import { decodeCartItemOpaqueId, decodeCartOpaqueId } from "../../xforms/id.js"; + +/** + * @name Mutation/setFulfillmentTypeForItems + * @method + * @memberof Cart/GraphQL + * @summary resolver for the setFulfillmentTypeForItems GraphQL mutation + * @param {Object} parentResult - unused + * @param {Object} args.input - an object of all mutation arguments that were sent by the client + * @param {String} args.input.cartId - The opaque ID of the cart to add the items to. + * @param {String} args.input.cartToken - The anonymous access cartToken that was returned from `createCart`. + * @param {String} args.input.fulfillmentType - The fulfillment type to be set fo all items. + * @param {String} [args.input.items] - An array of cart items to be assigned to the fulfillment type. + * @param {String} args.input.clientMutationId - An optional string identifying the mutation call + * @param {Object} context - an object containing the per-request state + * @returns {Promise} setFulfillmentTypeForItemsPayload + */ +export default async function setFulfillmentTypeForItems(parentResult, { input }, context) { + const { cartId: opaqueCartId, clientMutationId = null, fulfillmentType, items: itemsInput, cartToken } = input; + const cartId = decodeCartOpaqueId(opaqueCartId); + const items = itemsInput.map((item) => (decodeCartItemOpaqueId(item))); + + const { + cart + } = await context.mutations.setFulfillmentTypeForItems(context, { + cartId, + fulfillmentType, + items, + cartToken + }); + + return { + cart, + clientMutationId + }; +} diff --git a/packages/api-plugin-carts/src/schemas/cart.graphql b/packages/api-plugin-carts/src/schemas/cart.graphql index 284cb89bb6c..dfeacf89ddc 100644 --- a/packages/api-plugin-carts/src/schemas/cart.graphql +++ b/packages/api-plugin-carts/src/schemas/cart.graphql @@ -3,6 +3,9 @@ type Cart implements Node { "The Cart ID" _id: ID! + "The Cart version" + cartVersion: Int + """ The account that owns the cart. Some carts are created for anonymous users. Anonymous carts have a null account. Every account has exactly one cart per shop. @@ -230,6 +233,12 @@ type CartItem implements Node { "A title for use in cart/orders that conveys the selected product's title + chosen options" title: String! + "The fulfillment type of the item (if implemented in UI & selected by user) while adding item to cart" + selectedFulfillmentType: String + + "The fulfillment types supported by the item (received from Catalog)" + supportedFulfillmentTypes: [String] + "The date and time at which this item was last updated" updatedAt: DateTime! @@ -314,6 +323,9 @@ input CartItemInput { "The number of this item to add to the cart" quantity: Int! + + "The fulfillment type of the item (if implemented in UI & selected by user) while adding item to cart" + selectedFulfillmentType: String } "Input that defines a single configuration of a product" @@ -340,6 +352,24 @@ input AddCartItemsInput { items: [CartItemInput]! } +"Input for the `setFulfillmentTypeForItems` mutation" +input SetFulfillmentTypeForItemsInput { + "The cart ID" + cartId: ID! + + "If this cart is anonymous, provide the `cartToken` that was returned in the `CreateCartPayload`" + cartToken: String + + "An optional string identifying the mutation call, which will be returned in the response payload" + clientMutationId: String + + "Fulfillment Type to be assigned to the items" + fulfillmentType: String! + + "Array of item IDs for which selectedFulfillmentType has to be updated" + items: [String]! +} + "Input for the `removeCartItems` mutation" input RemoveCartItemsInput { "The cart ID" @@ -488,6 +518,17 @@ type CreateCartPayload { token: String } +"The payload returned from the `setFulfillmentTypeForItems` mutation call" +type SetFulfillmentTypeForItemsPayload { + """ + The modified cart. + """ + cart: Cart + + "The same string you sent with the mutation params, for matching mutation calls with their responses" + clientMutationId: String +} + "The payload returned from the `addCartItems` mutation call" type AddCartItemsPayload { """ @@ -571,6 +612,12 @@ extend type Mutation { input: CreateCartInput! ): CreateCartPayload! + "Updates the Selected fulfillment type and Fulfillment groups of the item(s)" + setFulfillmentTypeForItems( + "SetFulfillmentTypeForItems input" + input: SetFulfillmentTypeForItemsInput! + ): SetFulfillmentTypeForItemsPayload! + "Reconcile an anonymous cart with the current account cart for the same shop" reconcileCarts( "Mutation input" diff --git a/packages/api-plugin-carts/src/simpleSchemas.js b/packages/api-plugin-carts/src/simpleSchemas.js index 329b05b67a7..a593d70df39 100644 --- a/packages/api-plugin-carts/src/simpleSchemas.js +++ b/packages/api-plugin-carts/src/simpleSchemas.js @@ -192,6 +192,7 @@ const ShippoShippingMethod = new SimpleSchema({ * @type {SimpleSchema} * @property {String} _id Shipment method Id * @property {String} name Method name + * @property {String} fulfillmentMethod Method name identifier for app, not user editable * @property {String} label Public label * @property {String} group Group, allowed values: `Ground`, `Priority`, `One Day`, `Free` * @property {Number} cost optional @@ -211,7 +212,7 @@ const ShippoShippingMethod = new SimpleSchema({ * @property {String} carrier optional * @property {ShippoShippingMethod} settings optional */ -const ShippingMethod = new SimpleSchema({ +export const ShippingMethod = new SimpleSchema({ "_id": { type: String, label: "Shipment Method Id" @@ -225,6 +226,16 @@ const ShippingMethod = new SimpleSchema({ type: String, label: "Public Label" }, + "fulfillmentMethod": { + type: String, + optional: true, + label: "Method name identifier Internal" + }, + "displayMessageMethod": { + type: String, + optional: true, + label: "Display message to show in UI for this Method" + }, "group": { type: String, label: "Group", @@ -532,7 +543,7 @@ export const CartInvoice = new SimpleSchema({ * @property {String} customsLabelUrl For customs printable label * @property {ShippoShipment} shippo For Shippo specific properties */ -const Shipment = new SimpleSchema({ +export const Shipment = new SimpleSchema({ "_id": { type: String, label: "Shipment Id" @@ -571,9 +582,7 @@ const Shipment = new SimpleSchema({ optional: true }, "type": { - type: String, - allowedValues: ["shipping"], - defaultValue: "shipping" + type: String }, "parcel": { type: ShippingParcel, @@ -661,6 +670,8 @@ const CartItemAttribute = new SimpleSchema({ * @property {String} title Cart Item title * @property {Object} transaction Transaction associated with this item * @property {String} updatedAt required + * @property {String} selectedFulfillmentType Fulfillment Type (if selected/passed from UI) + * @property {String[]} supportedFulfillmentTypes Fulfillment Types supported by the item (received from Catalog) * @property {String} variantId required * @property {String} variantTitle Title from the selected variant */ @@ -733,6 +744,19 @@ export const CartItem = new SimpleSchema({ blackbox: true }, "updatedAt": Date, + "selectedFulfillmentType": { + type: String, + allowedValues: ["undecided"], // extended with dynamic values in fulfillment plugin startup + optional: true + }, + "supportedFulfillmentTypes": { + type: Array, + optional: true + }, + "supportedFulfillmentTypes.$": { + type: String, + allowedValues: ["undecided"] // extended with dynamic values in fulfillment plugin startup + }, "variantId": { type: String, optional: true @@ -767,6 +791,10 @@ export const Cart = new SimpleSchema({ type: String, optional: true }, + "cartVersion": { + type: Number, + optional: true + }, "shopId": { type: String, label: "Cart ShopId" diff --git a/packages/api-plugin-carts/src/util/addCartItems.js b/packages/api-plugin-carts/src/util/addCartItems.js index 552f2669009..09fe3a01d47 100644 --- a/packages/api-plugin-carts/src/util/addCartItems.js +++ b/packages/api-plugin-carts/src/util/addCartItems.js @@ -12,6 +12,10 @@ const inputItemSchema = new SimpleSchema({ type: Object, blackbox: true }, + "selectedFulfillmentType": { + type: String, + optional: true + }, "productConfiguration": Object, "productConfiguration.productId": String, "productConfiguration.productVariantId": String, @@ -47,7 +51,7 @@ export default async function addCartItems(context, currentItems, inputItems, op const currentDateTime = new Date(); const promises = inputItems.map(async (inputItem) => { - const { metafields, productConfiguration, quantity, price } = inputItem; + const { metafields, productConfiguration, quantity, price, selectedFulfillmentType } = inputItem; const { productId, productVariantId } = productConfiguration; // Get the published product from the DB, in order to get variant title and check price. @@ -111,6 +115,8 @@ export default async function addCartItems(context, currentItems, inputItems, op compareAtPrice: null, isTaxable: chosenVariant.isTaxable || false, metafields, + supportedFulfillmentTypes: catalogProduct.supportedFulfillmentTypes || [], + selectedFulfillmentType, optionTitle: chosenVariant.optionTitle, parcel: chosenVariant.parcel, // This one will be kept updated by event handler watching for diff --git a/packages/api-plugin-carts/src/util/updateCartFulfillmentGroups.js b/packages/api-plugin-carts/src/util/updateCartFulfillmentGroups.js index 7bc45b36ea4..5b8fb5da6ff 100644 --- a/packages/api-plugin-carts/src/util/updateCartFulfillmentGroups.js +++ b/packages/api-plugin-carts/src/util/updateCartFulfillmentGroups.js @@ -1,61 +1,124 @@ import Random from "@reactioncommerce/random"; +import ReactionError from "@reactioncommerce/reaction-error"; /** - * @summary Figures out which fulfillment group a cart item should initially be in + * @summary Figures out from which fulfillment groups the item should be removed * @param {Object[]} currentGroups The current cart fulfillment groups array - * @param {String[]} supportedFulfillmentTypes Array of fulfillment types supported by the item + * @param {String} itemId Id of the item to be removed + * @param {String} shopId The ID of the shop that owns the item (product) + * @returns {Object[]|null} The groups array or null if no viable group + */ +function determineRemoveFromGroups(currentGroups, itemId, shopId) { + let removeFromGroups = currentGroups.map((group) => { + if (group.itemIds && group.itemIds.includes(itemId) && shopId === group.shopId) { + return group; + } + return null; + }); + removeFromGroups = removeFromGroups.filter((group) => !!group); // remove nulls + // If the item is present in more that one Fulfillment group, we remove it from all(for backward compatibility) + return removeFromGroups || null; +} + +/** + * @summary Figures out which fulfillment group a cart item should be added to + * @param {Object[]} currentGroups The current cart fulfillment groups array + * @param {String[]} selectedFulfillmentType Selected Fulfillment type for the item * @param {String} shopId The ID of the shop that owns the item (product) * @returns {Object|null} The group or null if no viable group */ -function determineInitialGroupForItem(currentGroups, supportedFulfillmentTypes, shopId) { - const compatibleGroup = currentGroups.find((group) => supportedFulfillmentTypes.includes(group.type) && +function determineAddToGroup(currentGroups, selectedFulfillmentType, shopId) { + const compatibleGroup = currentGroups.find((group) => selectedFulfillmentType === group.type && shopId === group.shopId); return compatibleGroup || null; } +/** + * @summary Check if the provided fulfillment type is present in any of the groups and adds if not + * @param {Object[]} currentGroups The current cart fulfillment groups array + * @param {String} fulfillmentType Specific fulfillment type to be checked + * @param {Object} item The item (product) object + * @returns {undefined} + */ +function checkAndAddToGroup(currentGroups, fulfillmentType, item) { + let removeFromGroups = determineRemoveFromGroups(currentGroups, item._id, item.shopId); + if (removeFromGroups && removeFromGroups.length > 0) { + removeFromGroups = (removeFromGroups || []).map((group) => { + group.itemIds = (group.itemIds || []).filter((itemId) => item._id !== itemId); + return group; + }); + } + + const addToGroup = determineAddToGroup(currentGroups, fulfillmentType, item.shopId); + if (!addToGroup) { + // If no compatible group, add one with initially just this item in it + currentGroups.push({ + _id: Random.id(), + itemIds: [item._id], + shopId: item.shopId, + type: fulfillmentType + }); + } else if (!addToGroup.itemIds) { + // If there is a compatible group but it has no items array, add one with just this item in it + addToGroup.itemIds = [item._id]; + } else if (!addToGroup.itemIds.includes(item._id)) { + // If there is a compatible group with an items array but it is missing this item, add this item ID to the array + addToGroup.itemIds.push(item._id); + } +} + /** * @summary Updates the `shipping` property on a `cart` * @param {Object} context App context * @param {Object} cart The cart, to be mutated * @returns {undefined} */ -export default function updateCartFulfillmentGroups(context, cart) { +export default async function updateCartFulfillmentGroups(context, cart) { // Every time the cart is updated, create any missing fulfillment groups as necessary. // We need one group per type per shop, containing only the items from that shop. // Also make sure that every item is assigned to a fulfillment group. + const { collections } = context; + const { Fulfillment } = collections; const currentGroups = cart.shipping || []; - (cart.items || []).forEach((item) => { + for (const item of (cart.items || [])) { let { supportedFulfillmentTypes } = item; - // Do not re-allocate the item if it is already in the group. Otherwise difficult for other code - // to create and manage fulfillment groups - const itemAlreadyInTheGroup = currentGroups.find(({ itemIds }) => itemIds && itemIds.includes(item._id)); - if (itemAlreadyInTheGroup) return; + + // This is a new optional field that UI can pass in case the user selects fulfillment type + // for each item in the product details page instead of waiting till checkout + let { selectedFulfillmentType } = item; if (!supportedFulfillmentTypes || supportedFulfillmentTypes.length === 0) { - supportedFulfillmentTypes = ["shipping"]; + if (!cart.cartVersion) { + supportedFulfillmentTypes = ["shipping"]; // we use 'shipping' as default fulfillment type for old v1 carts (prior to fulfillment types) + } else { + throw new ReactionError("not-found", "Product does not have any supported FulfillmentTypes"); + } } - // Out of the current groups, returns the one that this item should be in by default, if it isn't - // already in a group - const group = determineInitialGroupForItem(currentGroups, supportedFulfillmentTypes, item.shopId); + if (selectedFulfillmentType && !supportedFulfillmentTypes.includes(selectedFulfillmentType)) { + throw new ReactionError("not-found", "Selected fulfillmentType is not supported by the Product"); + } - if (!group) { - // If no compatible group, add one with initially just this item in it - currentGroups.push({ - _id: Random.id(), - itemIds: [item._id], - shopId: item.shopId, - type: supportedFulfillmentTypes[0] - }); - } else if (!group.itemIds) { - // If there is a compatible group but it has no items array, add one with just this item in it - group.itemIds = [item._id]; - } else if (!group.itemIds.includes(item._id)) { - // If there is a compatible group with an items array but it is missing this item, add this item ID to the array - group.itemIds.push(item._id); + // When selectedFulfillmentType is not available, if the product only supports ONE fulfillment type, use that + // If more than one fulfillment type is available, then add item to undecided group + if (!selectedFulfillmentType) { + if (supportedFulfillmentTypes.length === 1) { + ([selectedFulfillmentType] = supportedFulfillmentTypes); + } else { + selectedFulfillmentType = "undecided"; + } } - }); + // check if the selectedFulfillmentType is an 'enabled' fulfillmentType, if not set is 'undecided' + /* eslint-disable no-await-in-loop */ + const enabledFulfillmentTypeObjs = await Fulfillment.find({ "shopId": item.shopId, "provider.enabled": true }).toArray(); + /* eslint-enable no-await-in-loop */ + let enabledFulfillmentTypes = (enabledFulfillmentTypeObjs || []).map((ffType) => ffType.fulfillmentType); + enabledFulfillmentTypes = (enabledFulfillmentTypes || []).filter((val) => !!val); // Remove nulls + if (!enabledFulfillmentTypes.includes(selectedFulfillmentType)) selectedFulfillmentType = "undecided"; + + checkAndAddToGroup(currentGroups, selectedFulfillmentType, item); + } // Items may also have been removed. Need to remove their IDs from each group.itemIds currentGroups.forEach((group) => { diff --git a/packages/api-plugin-carts/src/util/xformCartGroupToCommonOrder.js b/packages/api-plugin-carts/src/util/xformCartGroupToCommonOrder.js index d18e3969eb3..8c568f80d5b 100644 --- a/packages/api-plugin-carts/src/util/xformCartGroupToCommonOrder.js +++ b/packages/api-plugin-carts/src/util/xformCartGroupToCommonOrder.js @@ -75,6 +75,7 @@ export default async function xformCartGroupToCommonOrder(cart, group, context) total: null }; let fulfillmentMethodId; + let methodAdditionalData; if (shipmentMethod) { fulfillmentPrices = { @@ -93,6 +94,7 @@ export default async function xformCartGroupToCommonOrder(cart, group, context) }; fulfillmentMethodId = shipmentMethod._id; + methodAdditionalData = shipmentMethod.methodAdditionalData || { gqlType: "emptyData", emptyData: false }; } // TODO: In the future, we should update this with a discounts update @@ -138,6 +140,7 @@ export default async function xformCartGroupToCommonOrder(cart, group, context) fulfillmentMethodId, fulfillmentPrices, fulfillmentType, + methodAdditionalData, items, orderId: null, originAddress: (shop && Array.isArray(shop.addressBook) && shop.addressBook[0]) || null, diff --git a/packages/api-plugin-carts/src/xforms/xformCartCheckout.js b/packages/api-plugin-carts/src/xforms/xformCartCheckout.js index 707f41ad66b..1aa95bd1ecc 100644 --- a/packages/api-plugin-carts/src/xforms/xformCartCheckout.js +++ b/packages/api-plugin-carts/src/xforms/xformCartCheckout.js @@ -14,6 +14,7 @@ function xformCartFulfillmentGroup(fulfillmentGroup, cart) { displayName: option.method.label || option.method.name, group: option.method.group || null, name: option.method.name, + methodAdditionalData: option.method.methodAdditionalData || { gqlType: "emptyData", emptyData: false }, fulfillmentTypes: option.method.fulfillmentTypes }, handlingPrice: { @@ -39,6 +40,7 @@ function xformCartFulfillmentGroup(fulfillmentGroup, cart) { displayName: fulfillmentGroup.shipmentMethod.label || fulfillmentGroup.shipmentMethod.name, group: fulfillmentGroup.shipmentMethod.group || null, name: fulfillmentGroup.shipmentMethod.name, + methodAdditionalData: fulfillmentGroup.shipmentMethod.methodAdditionalData || { gqlType: "emptyData", emptyData: false }, fulfillmentTypes: fulfillmentGroup.shipmentMethod.fulfillmentTypes }, handlingPrice: { @@ -65,7 +67,7 @@ function xformCartFulfillmentGroup(fulfillmentGroup, cart) { shippingAddress: fulfillmentGroup.address, shopId: fulfillmentGroup.shopId, // For now, this is always shipping. Revisit when adding download, pickup, etc. types - type: "shipping" + type: fulfillmentGroup.type }; } From d2d37ec3c50dc468ce5320c1fa68887c95a007e7 Mon Sep 17 00:00:00 2001 From: Sujith Date: Thu, 20 Oct 2022 17:53:02 +0530 Subject: [PATCH 26/71] feat: changes related to catalogs plugin Signed-off-by: Sujith --- packages/api-plugin-catalogs/src/simpleSchemas.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/api-plugin-catalogs/src/simpleSchemas.js b/packages/api-plugin-catalogs/src/simpleSchemas.js index bea3bb612c7..da40f240aec 100644 --- a/packages/api-plugin-catalogs/src/simpleSchemas.js +++ b/packages/api-plugin-catalogs/src/simpleSchemas.js @@ -468,8 +468,7 @@ export const CatalogProduct = new SimpleSchema({ }, "supportedFulfillmentTypes": { type: Array, - label: "Supported fulfillment types", - defaultValue: ["shipping"] + label: "Supported fulfillment types" }, "supportedFulfillmentTypes.$": String, "tagIds": { From b455518de3097244eecc05b50de7dd6d70985393 Mon Sep 17 00:00:00 2001 From: Sujith Date: Thu, 20 Oct 2022 17:53:24 +0530 Subject: [PATCH 27/71] feat: changes related to orders plugin Signed-off-by: Sujith --- packages/api-plugin-orders/src/index.js | 7 +++++-- .../src/mutations/placeOrder.test.js | 2 +- packages/api-plugin-orders/src/simpleSchemas.js | 11 ++++------- .../src/util/xformOrderGroupToCommonOrder.js | 3 +++ .../xformOrderFulfillmentGroupSelectedOption.js | 8 +++++--- 5 files changed, 18 insertions(+), 13 deletions(-) diff --git a/packages/api-plugin-orders/src/index.js b/packages/api-plugin-orders/src/index.js index 9db9d5bb39a..756eb8ba789 100644 --- a/packages/api-plugin-orders/src/index.js +++ b/packages/api-plugin-orders/src/index.js @@ -6,7 +6,7 @@ import preStartup from "./preStartup.js"; import queries from "./queries/index.js"; import resolvers from "./resolvers/index.js"; import schemas from "./schemas/index.js"; -import { Order, OrderFulfillmentGroup, OrderItem } from "./simpleSchemas.js"; +import { Order, OrderFulfillmentGroup, OrderItem, CommonOrder, orderFulfillmentGroupInputSchema, SelectedFulfillmentOption } from "./simpleSchemas.js"; import startup from "./startup.js"; import getDataForOrderEmail from "./util/getDataForOrderEmail.js"; @@ -56,7 +56,10 @@ export default async function register(app) { simpleSchemas: { Order, OrderFulfillmentGroup, - OrderItem + OrderItem, + CommonOrder, + orderFulfillmentGroupInputSchema, + SelectedFulfillmentOption } }); } diff --git a/packages/api-plugin-orders/src/mutations/placeOrder.test.js b/packages/api-plugin-orders/src/mutations/placeOrder.test.js index ee32ea98641..2f9e5e625e8 100644 --- a/packages/api-plugin-orders/src/mutations/placeOrder.test.js +++ b/packages/api-plugin-orders/src/mutations/placeOrder.test.js @@ -155,7 +155,7 @@ test("places an anonymous $0 order with no cartId and no payments", async () => }, shopId: orderInput.shopId, totalItemQuantity: 1, - type: "shipping", + type: "mockType", workflow: { status: "new", workflow: [ diff --git a/packages/api-plugin-orders/src/simpleSchemas.js b/packages/api-plugin-orders/src/simpleSchemas.js index 43767547862..2ad959fc4ac 100644 --- a/packages/api-plugin-orders/src/simpleSchemas.js +++ b/packages/api-plugin-orders/src/simpleSchemas.js @@ -332,8 +332,7 @@ export const CommonOrder = new SimpleSchema({ }, fulfillmentPrices: CommonOrderFulfillmentPrices, fulfillmentType: { - type: String, - allowedValues: ["shipping"] + type: String }, items: [CommonOrderItem], orderId: { @@ -392,8 +391,7 @@ export const orderFulfillmentGroupInputSchema = new SimpleSchema({ optional: true }, "type": { - type: String, - allowedValues: ["shipping"] + type: String } }); @@ -773,7 +771,7 @@ export const OrderItem = new SimpleSchema({ * @property {String} name Method name * @property {Number} rate Rate */ -const SelectedFulfillmentOption = new SimpleSchema({ +export const SelectedFulfillmentOption = new SimpleSchema({ _id: String, carrier: { type: String, @@ -853,8 +851,7 @@ export const OrderFulfillmentGroup = new SimpleSchema({ optional: true }, "type": { - type: String, - allowedValues: ["shipping"] + type: String }, "updatedAt": { type: Date, diff --git a/packages/api-plugin-orders/src/util/xformOrderGroupToCommonOrder.js b/packages/api-plugin-orders/src/util/xformOrderGroupToCommonOrder.js index 75e12d92d2c..b08514b3616 100644 --- a/packages/api-plugin-orders/src/util/xformOrderGroupToCommonOrder.js +++ b/packages/api-plugin-orders/src/util/xformOrderGroupToCommonOrder.js @@ -54,6 +54,7 @@ export default async function xformOrderGroupToCommonOrder({ total: null }; let fulfillmentMethodId; + let methodAdditionalData; if (shipmentMethod) { fulfillmentPrices = { @@ -72,6 +73,7 @@ export default async function xformOrderGroupToCommonOrder({ }; fulfillmentMethodId = shipmentMethod._id; + methodAdditionalData = shipmentMethod.methodAdditionalData || { gqlType: "emptyData", emptyData: false }; } // TODO: In the future, we should update this with a discounts update @@ -115,6 +117,7 @@ export default async function xformOrderGroupToCommonOrder({ fulfillmentMethodId, fulfillmentPrices, fulfillmentType, + methodAdditionalData, items, orderId, originAddress: (shop && Array.isArray(shop.addressBook) && shop.addressBook[0]) || null, diff --git a/packages/api-plugin-orders/src/xforms/xformOrderFulfillmentGroupSelectedOption.js b/packages/api-plugin-orders/src/xforms/xformOrderFulfillmentGroupSelectedOption.js index 4ef4ef317a6..9bb7ea059f3 100644 --- a/packages/api-plugin-orders/src/xforms/xformOrderFulfillmentGroupSelectedOption.js +++ b/packages/api-plugin-orders/src/xforms/xformOrderFulfillmentGroupSelectedOption.js @@ -1,9 +1,11 @@ /** * @summary Transform a single fulfillment group fulfillment option * @param {Object} fulfillmentOption The group.shipmentMethod + * @param {Object} node The group * @returns {Object} Transformed fulfillment option */ -export default function xformOrderFulfillmentGroupSelectedOption(fulfillmentOption) { +export default function xformOrderFulfillmentGroupSelectedOption(fulfillmentOption, node) { + const fulfillmentType = (node && node.type) ? node.type : ""; return { fulfillmentMethod: { _id: fulfillmentOption._id, @@ -11,8 +13,8 @@ export default function xformOrderFulfillmentGroupSelectedOption(fulfillmentOpti displayName: fulfillmentOption.label || fulfillmentOption.name, group: fulfillmentOption.group || null, name: fulfillmentOption.name, - // For now, this is always shipping. Revisit when adding download, pickup, etc. types - fulfillmentTypes: ["shipping"] + fulfillmentTypes: [fulfillmentType], + methodAdditionalData: fulfillmentOption.methodAdditionalData || { gqlType: "emptyData", emptyData: false } }, handlingPrice: { amount: fulfillmentOption.handling || 0, From d8f2465db02a1cc40ea66681e36207eee3ad8f22 Mon Sep 17 00:00:00 2001 From: Sujith Date: Thu, 20 Oct 2022 17:54:46 +0530 Subject: [PATCH 28/71] feat: changes related to products plugin Signed-off-by: Sujith --- packages/api-plugin-products/src/mutations/createProduct.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/api-plugin-products/src/mutations/createProduct.js b/packages/api-plugin-products/src/mutations/createProduct.js index b9534a77a43..2d6068bc4d6 100644 --- a/packages/api-plugin-products/src/mutations/createProduct.js +++ b/packages/api-plugin-products/src/mutations/createProduct.js @@ -59,7 +59,7 @@ export default async function createProduct(context, input) { isVisible: false, shopId, shouldAppearInSitemap: true, - supportedFulfillmentTypes: ["shipping"], + supportedFulfillmentTypes: initialProductData.supportedFulfillmentTypes || [], title: "", type: "simple", updatedAt: createdAt, From 25a91b6616c4d2f42c26a7b3100637e5cf670cf0 Mon Sep 17 00:00:00 2001 From: Sujith Date: Thu, 20 Oct 2022 18:59:57 +0530 Subject: [PATCH 29/71] feat: integration test updates Signed-off-by: Sujith --- .../addOrderFulfillmentGroup.test.js | 36 ++++++++++++++++++- .../checkout/anonymousCheckout.test.js | 8 ++++- .../anonymousToAuthenticatedCheckout.test.js | 8 ++++- .../checkout/authenticatedCheckout.test.js | 8 ++++- .../mutations/checkout/checkoutTestsCommon.js | 4 ++- .../createProduct/createProduct.test.js | 2 +- .../moveOrderItems/moveOrderItems.test.js | 36 ++++++++++++++++++- .../createFlatRateFulfillmentMethod.test.js | 2 +- ...eateFlatRateFulfillmentRestriction.test.js | 4 +-- .../deleteFlatRateFulfillmentMethod.test.js | 6 ++-- ...leteFlatRateFulfillmentRestriction.test.js | 4 +-- .../updateFlatRateFulfillmentMethod.test.js | 4 +-- ...dateFlatRateFulfillmentRestriction.test.js | 4 +-- .../splitOrderItem/splitOrderItem.test.js | 35 +++++++++++++++++- .../getFlatRateFulfillmentRestriction.test.js | 4 +-- ...getFlatRateFulfillmentRestrictions.test.js | 4 +-- .../flatRateFulfillmentMethod.test.js | 4 +-- .../flatRateFulfillmentMethods.test.js | 6 ++-- apps/reaction/tests/util/factory.js | 16 +++++++++ 19 files changed, 167 insertions(+), 28 deletions(-) diff --git a/apps/reaction/tests/integration/api/mutations/addOrderFulfillmentGroup/addOrderFulfillmentGroup.test.js b/apps/reaction/tests/integration/api/mutations/addOrderFulfillmentGroup/addOrderFulfillmentGroup.test.js index fd4d40f4ac1..c96764b2e5e 100644 --- a/apps/reaction/tests/integration/api/mutations/addOrderFulfillmentGroup/addOrderFulfillmentGroup.test.js +++ b/apps/reaction/tests/integration/api/mutations/addOrderFulfillmentGroup/addOrderFulfillmentGroup.test.js @@ -24,9 +24,37 @@ const mockShipmentMethod = { handling: 0, label: "mockLabel", name: "mockName", + fulfillmentMethod: "flatRate", rate: 3.99 }; +const mockFulfillmentEntry = { + _id: "mockShippingMethod", + name: "Default Shipping Provider", + provider: { + enabled: true, + label: "Flat Rate", + name: "flatRates" + }, + fulfillmentType: "shipping", + methods: [ + { + cost: 2.5, + fulfillmentTypes: [ + "shipping" + ], + fulfillmentMethod: "flatRate", + group: "Ground", + handling: 1.5, + label: "Standard mockMethod", + name: "mockMethod", + rate: 1, + _id: "METHOD_ID", + enabled: true + } + ] +}; + const mockInvoice = Factory.OrderInvoice.makeOne({ currencyCode: "USD", // Need to ensure 0 discount to avoid creating negative totals @@ -58,7 +86,7 @@ beforeAll(async () => { testApp.registerPlugin({ name: "addOrderFulfillmentGroup.test.js", functionsByType: { - getFulfillmentMethodsWithQuotes: [getFulfillmentMethodsWithQuotes] + getFulfillmentMethodsWithQuotesShipping: [getFulfillmentMethodsWithQuotes] } }); @@ -66,6 +94,10 @@ beforeAll(async () => { shopId = await insertPrimaryShop(testApp.context); + // Add shipping methods + mockFulfillmentEntry.shopId = shopId; + await testApp.collections.Fulfillment.insertOne(mockFulfillmentEntry); + const adminGroup = Factory.Group.makeOne({ _id: "adminGroup", createdBy: null, @@ -165,6 +197,7 @@ test("user with `reaction:legacy:orders/update` role can add an order fulfillmen const group = Factory.OrderFulfillmentGroup.makeOne({ invoice: mockInvoice, items: [orderItem], + type: "shipping", shopId }); @@ -303,6 +336,7 @@ test("user with `reaction:legacy:orders/move:item` role can add an order fulfill currencyCode: "USD" }, shopId, + type: "shipping", totalItemQuantity: 12 }); diff --git a/apps/reaction/tests/integration/api/mutations/checkout/anonymousCheckout.test.js b/apps/reaction/tests/integration/api/mutations/checkout/anonymousCheckout.test.js index 00205030640..ce644bd22f8 100644 --- a/apps/reaction/tests/integration/api/mutations/checkout/anonymousCheckout.test.js +++ b/apps/reaction/tests/integration/api/mutations/checkout/anonymousCheckout.test.js @@ -1,3 +1,4 @@ +import encodeOpaqueId from "@reactioncommerce/api-utils/encodeOpaqueId.js"; import importAsString from "@reactioncommerce/api-utils/importAsString.js"; import getCommonData from "./checkoutTestsCommon.js"; @@ -269,7 +270,12 @@ describe("as an anonymous user", () => { return; } - const option = result.updateFulfillmentOptionsForGroup.cart.checkout.fulfillmentGroups[0].availableFulfillmentOptions[0]; + const mockFulfillmentMethodId = "mockMethod"; + const opaqueMockFulfillmentMethodId = encodeOpaqueId("reaction/fulfillmentMethod", mockFulfillmentMethodId); + + // From the multiple Fulfillment methods, find out the exact method we are looking for + const options = result.updateFulfillmentOptionsForGroup.cart.checkout.fulfillmentGroups[0].availableFulfillmentOptions; + const option = options.find((opt) => opt.fulfillmentMethod._id === opaqueMockFulfillmentMethodId); opaqueFulfillmentMethodId = option.fulfillmentMethod._id; expect(option).toEqual({ diff --git a/apps/reaction/tests/integration/api/mutations/checkout/anonymousToAuthenticatedCheckout.test.js b/apps/reaction/tests/integration/api/mutations/checkout/anonymousToAuthenticatedCheckout.test.js index 70931cbebc4..63a17e4aa9d 100644 --- a/apps/reaction/tests/integration/api/mutations/checkout/anonymousToAuthenticatedCheckout.test.js +++ b/apps/reaction/tests/integration/api/mutations/checkout/anonymousToAuthenticatedCheckout.test.js @@ -122,6 +122,7 @@ describe("as an anonymous user", () => { productId: opaqueProductId, productVariantId: opaqueCartProductVariantId }, + selectedFulfillmentType: "shipping", quantity: 1 } } @@ -294,7 +295,12 @@ describe("as an anonymous user", () => { return; } - const option = result.updateFulfillmentOptionsForGroup.cart.checkout.fulfillmentGroups[0].availableFulfillmentOptions[0]; + const mockFulfillmentMethodId = "mockMethod"; + const opaqueMockFulfillmentMethodId = encodeOpaqueId("reaction/fulfillmentMethod", mockFulfillmentMethodId); + + // From the multiple Fulfillment methods, find out the exact method we are looking for + const options = result.updateFulfillmentOptionsForGroup.cart.checkout.fulfillmentGroups[0].availableFulfillmentOptions; + const option = options.find((opt) => opt.fulfillmentMethod._id === opaqueMockFulfillmentMethodId); opaqueFulfillmentMethodId = option.fulfillmentMethod._id; expect(option).toEqual({ diff --git a/apps/reaction/tests/integration/api/mutations/checkout/authenticatedCheckout.test.js b/apps/reaction/tests/integration/api/mutations/checkout/authenticatedCheckout.test.js index b5ac5d7f944..e754d433af4 100644 --- a/apps/reaction/tests/integration/api/mutations/checkout/authenticatedCheckout.test.js +++ b/apps/reaction/tests/integration/api/mutations/checkout/authenticatedCheckout.test.js @@ -119,6 +119,7 @@ describe("as a signed in user", () => { productId: opaqueProductId, productVariantId: opaqueCartProductVariantId }, + selectedFulfillmentType: "shipping", quantity: 1 } } @@ -293,7 +294,12 @@ describe("as a signed in user", () => { return; } - const option = result.updateFulfillmentOptionsForGroup.cart.checkout.fulfillmentGroups[0].availableFulfillmentOptions[0]; + const mockFulfillmentMethodId = "mockMethod"; + const opaqueMockFulfillmentMethodId = encodeOpaqueId("reaction/fulfillmentMethod", mockFulfillmentMethodId); + + // From the multiple Fulfillment methods, find out the exact method we are looking for + const options = result.updateFulfillmentOptionsForGroup.cart.checkout.fulfillmentGroups[0].availableFulfillmentOptions; + const option = options.find((opt) => opt.fulfillmentMethod._id === opaqueMockFulfillmentMethodId); opaqueFulfillmentMethodId = option.fulfillmentMethod._id; expect(option).toEqual({ diff --git a/apps/reaction/tests/integration/api/mutations/checkout/checkoutTestsCommon.js b/apps/reaction/tests/integration/api/mutations/checkout/checkoutTestsCommon.js index 03651a4d8c7..7a48df6ff3a 100644 --- a/apps/reaction/tests/integration/api/mutations/checkout/checkoutTestsCommon.js +++ b/apps/reaction/tests/integration/api/mutations/checkout/checkoutTestsCommon.js @@ -73,12 +73,14 @@ const mockShippingMethod = { label: "Flat Rate", name: "flatRates" }, + fulfillmentType: "shipping", methods: [ { cost: 2.5, fulfillmentTypes: [ "shipping" ], + fulfillmentMethod: "flatRate", group: "Ground", handling: 1.5, label: "Standard mockMethod", @@ -186,7 +188,7 @@ beforeAll(async () => { // Add shipping methods mockShippingMethod.shopId = internalShopId; - await testApp.collections.Shipping.insertOne(mockShippingMethod); + await testApp.collections.Fulfillment.insertOne(mockShippingMethod); // Add Tags and products mockProduct.shopId = internalShopId; diff --git a/apps/reaction/tests/integration/api/mutations/createProduct/createProduct.test.js b/apps/reaction/tests/integration/api/mutations/createProduct/createProduct.test.js index 63d9637f634..4917d87e9fb 100644 --- a/apps/reaction/tests/integration/api/mutations/createProduct/createProduct.test.js +++ b/apps/reaction/tests/integration/api/mutations/createProduct/createProduct.test.js @@ -37,7 +37,7 @@ const expectedProduct = { { service: "pinterest", message: "" }, { service: "twitter", message: "" } ], - supportedFulfillmentTypes: ["shipping"], + supportedFulfillmentTypes: [], tagIds: [], title: "", updatedAt: jasmine.any(String), diff --git a/apps/reaction/tests/integration/api/mutations/moveOrderItems/moveOrderItems.test.js b/apps/reaction/tests/integration/api/mutations/moveOrderItems/moveOrderItems.test.js index 1b7892359b8..3bb563a302d 100644 --- a/apps/reaction/tests/integration/api/mutations/moveOrderItems/moveOrderItems.test.js +++ b/apps/reaction/tests/integration/api/mutations/moveOrderItems/moveOrderItems.test.js @@ -19,9 +19,37 @@ const mockShipmentMethod = { handling: 0, label: "mockLabel", name: "mockName", + fulfillmentMethod: "flatRate", rate: 3.99 }; +const mockFulfillmentEntry = { + _id: "mockShippingMethod", + name: "Default Shipping Provider", + provider: { + enabled: true, + label: "Flat Rate", + name: "flatRates" + }, + fulfillmentType: "shipping", + methods: [ + { + cost: 2.5, + fulfillmentTypes: [ + "shipping" + ], + fulfillmentMethod: "flatRate", + group: "Ground", + handling: 1.5, + label: "Standard mockMethod", + name: "mockMethod", + rate: 1, + _id: "METHOD_ID", + enabled: true + } + ] +}; + beforeAll(async () => { const getFulfillmentMethodsWithQuotes = (context, commonOrderExtended, [rates]) => { rates.push({ @@ -46,13 +74,17 @@ beforeAll(async () => { testApp.registerPlugin({ name: "moveOrderItems.test.js", functionsByType: { - getFulfillmentMethodsWithQuotes: [getFulfillmentMethodsWithQuotes] + getFulfillmentMethodsWithQuotesShipping: [getFulfillmentMethodsWithQuotes] } }); await testApp.start(); shopId = await insertPrimaryShop(testApp.context); + // Add shipping methods + mockFulfillmentEntry.shopId = shopId; + await testApp.collections.Fulfillment.insertOne(mockFulfillmentEntry); + catalogItem = Factory.Catalog.makeOne({ isDeleted: false, product: Factory.CatalogProduct.makeOne({ @@ -124,6 +156,7 @@ test("user who placed an order can move an order item", async () => { ...mockShipmentMethod, currencyCode: "USD" }, + type: "shipping", shopId }); @@ -138,6 +171,7 @@ test("user who placed an order can move an order item", async () => { ...mockShipmentMethod, currencyCode: "USD" }, + type: "shipping", shopId }); diff --git a/apps/reaction/tests/integration/api/mutations/shippingRates/createFlatRateFulfillmentMethod.test.js b/apps/reaction/tests/integration/api/mutations/shippingRates/createFlatRateFulfillmentMethod.test.js index adf40ff1653..5742d05bde6 100644 --- a/apps/reaction/tests/integration/api/mutations/shippingRates/createFlatRateFulfillmentMethod.test.js +++ b/apps/reaction/tests/integration/api/mutations/shippingRates/createFlatRateFulfillmentMethod.test.js @@ -28,7 +28,7 @@ const adminGroup = Factory.Group.makeOne({ _id: "adminGroup", createdBy: null, name: "admin", - permissions: ["reaction:legacy:shippingMethods/create"], + permissions: ["reaction:legacy:fulfillmentMethods/create"], slug: "admin", shopId: internalShopId }); diff --git a/apps/reaction/tests/integration/api/mutations/shippingRates/createFlatRateFulfillmentRestriction.test.js b/apps/reaction/tests/integration/api/mutations/shippingRates/createFlatRateFulfillmentRestriction.test.js index 6b6ec5a02f5..dacdcd04adb 100644 --- a/apps/reaction/tests/integration/api/mutations/shippingRates/createFlatRateFulfillmentRestriction.test.js +++ b/apps/reaction/tests/integration/api/mutations/shippingRates/createFlatRateFulfillmentRestriction.test.js @@ -32,7 +32,7 @@ const adminGroup = Factory.Group.makeOne({ _id: "adminGroup", createdBy: null, name: "admin", - permissions: ["reaction:legacy:shippingRestrictions/create"], + permissions: ["reaction:legacy:fulfillmentRestrictions/create"], slug: "admin", shopId: internalShopId }); @@ -81,7 +81,7 @@ test("shop owner cannot update flat rate fulfillment restriction if not logged i } }); -test("user with `reaction:legacy:shippingMethods/create` permissions can update flat rate fulfillment restriction", async () => { +test("user with `reaction:legacy:fulfillmentMethods/create` permissions can update flat rate fulfillment restriction", async () => { let result; await testApp.setLoggedInUser(mockOwnerAccount); diff --git a/apps/reaction/tests/integration/api/mutations/shippingRates/deleteFlatRateFulfillmentMethod.test.js b/apps/reaction/tests/integration/api/mutations/shippingRates/deleteFlatRateFulfillmentMethod.test.js index bab240b6f9f..15bfab49c01 100644 --- a/apps/reaction/tests/integration/api/mutations/shippingRates/deleteFlatRateFulfillmentMethod.test.js +++ b/apps/reaction/tests/integration/api/mutations/shippingRates/deleteFlatRateFulfillmentMethod.test.js @@ -37,7 +37,7 @@ const adminGroup = Factory.Group.makeOne({ _id: "adminGroup", createdBy: null, name: "admin", - permissions: ["reaction:legacy:shippingMethods/delete"], + permissions: ["reaction:legacy:fulfillmentMethods/delete"], slug: "admin", shopId: internalShopId }); @@ -80,7 +80,7 @@ beforeAll(async () => { await testApp.collections.Groups.insertOne(adminGroup); await testApp.collections.Groups.insertOne(customerGroup); - await testApp.collections.Shipping.insertOne({ + await testApp.collections.Fulfillment.insertOne({ methods: [{ _id: mockFulfillmentMethodId, shopId: internalShopId, @@ -114,7 +114,7 @@ test("user can not delete flat rate fulfillment method if admin is not logged in } }); -test("user can delete flat rate fulfillment method if they have `reaction:legacy:shippingMethods/delete` permissions", async () => { +test("user can delete flat rate fulfillment method if they have `reaction:legacy:fulfillmentMethods/delete` permissions", async () => { await testApp.setLoggedInUser(mockAdminAccount); let result; diff --git a/apps/reaction/tests/integration/api/mutations/shippingRates/deleteFlatRateFulfillmentRestriction.test.js b/apps/reaction/tests/integration/api/mutations/shippingRates/deleteFlatRateFulfillmentRestriction.test.js index ad8cab78b2d..8cea9e0afa1 100644 --- a/apps/reaction/tests/integration/api/mutations/shippingRates/deleteFlatRateFulfillmentRestriction.test.js +++ b/apps/reaction/tests/integration/api/mutations/shippingRates/deleteFlatRateFulfillmentRestriction.test.js @@ -36,7 +36,7 @@ const adminGroup = Factory.Group.makeOne({ _id: "adminGroup", createdBy: null, name: "admin", - permissions: ["reaction:legacy:shippingRestrictions/delete"], + permissions: ["reaction:legacy:fulfillmentRestrictions/delete"], slug: "admin", shopId: internalShopId }); @@ -58,7 +58,7 @@ beforeAll(async () => { await testApp.start(); await insertPrimaryShop(testApp.context, { _id: internalShopId, name: shopName }); - await testApp.collections.FlatRateFulfillmentRestrictions.insertOne(mockFulfillmentRestriction); + await testApp.collections.FulfillmentRestrictions.insertOne(mockFulfillmentRestriction); await testApp.collections.Groups.insertOne(adminGroup); await testApp.createUserAndAccount(mockOwnerAccount); diff --git a/apps/reaction/tests/integration/api/mutations/shippingRates/updateFlatRateFulfillmentMethod.test.js b/apps/reaction/tests/integration/api/mutations/shippingRates/updateFlatRateFulfillmentMethod.test.js index bc34ebf6fe8..2245950ada5 100644 --- a/apps/reaction/tests/integration/api/mutations/shippingRates/updateFlatRateFulfillmentMethod.test.js +++ b/apps/reaction/tests/integration/api/mutations/shippingRates/updateFlatRateFulfillmentMethod.test.js @@ -44,7 +44,7 @@ const adminGroup = Factory.Group.makeOne({ _id: "adminGroup", createdBy: null, name: "admin", - permissions: ["reaction:legacy:shippingMethods/update"], + permissions: ["reaction:legacy:fulfillmentMethods/update"], slug: "admin", shopId: internalShopId }); @@ -87,7 +87,7 @@ beforeAll(async () => { await testApp.collections.Groups.insertOne(adminGroup); await testApp.collections.Groups.insertOne(customerGroup); - await testApp.collections.Shipping.insertOne({ + await testApp.collections.Fulfillment.insertOne({ methods: [mockFulfillmentMethod], shopId: internalShopId }); diff --git a/apps/reaction/tests/integration/api/mutations/shippingRates/updateFlatRateFulfillmentRestriction.test.js b/apps/reaction/tests/integration/api/mutations/shippingRates/updateFlatRateFulfillmentRestriction.test.js index dac138c5b20..851c88329d7 100644 --- a/apps/reaction/tests/integration/api/mutations/shippingRates/updateFlatRateFulfillmentRestriction.test.js +++ b/apps/reaction/tests/integration/api/mutations/shippingRates/updateFlatRateFulfillmentRestriction.test.js @@ -34,7 +34,7 @@ const adminGroup = Factory.Group.makeOne({ _id: "adminGroup", createdBy: null, name: "admin", - permissions: ["reaction:legacy:shippingRestrictions/update"], + permissions: ["reaction:legacy:fulfillmentRestrictions/update"], slug: "admin", shopId: internalShopId }); @@ -68,7 +68,7 @@ beforeAll(async () => { await testApp.start(); await insertPrimaryShop(testApp.context, { _id: internalShopId, name: shopName }); - await testApp.collections.FlatRateFulfillmentRestrictions.insertOne(mockFulfillmentRestriction); + await testApp.collections.FulfillmentRestrictions.insertOne(mockFulfillmentRestriction); await testApp.collections.Groups.insertOne(adminGroup); await testApp.createUserAndAccount(mockOwnerAccount); diff --git a/apps/reaction/tests/integration/api/mutations/splitOrderItem/splitOrderItem.test.js b/apps/reaction/tests/integration/api/mutations/splitOrderItem/splitOrderItem.test.js index d9f5b453574..5f06f3b7cce 100644 --- a/apps/reaction/tests/integration/api/mutations/splitOrderItem/splitOrderItem.test.js +++ b/apps/reaction/tests/integration/api/mutations/splitOrderItem/splitOrderItem.test.js @@ -20,9 +20,37 @@ const mockShipmentMethod = { handling: 0, label: "mockLabel", name: "mockName", + fulfillmentMethod: "flatRate", rate: 3.99 }; +const mockFulfillmentEntry = { + _id: "mockShippingMethod", + name: "Default Shipping Provider", + provider: { + enabled: true, + label: "Flat Rate", + name: "flatRates" + }, + fulfillmentType: "shipping", + methods: [ + { + cost: 2.5, + fulfillmentTypes: [ + "shipping" + ], + fulfillmentMethod: "flatRate", + group: "Ground", + handling: 1.5, + label: "Standard mockMethod", + name: "mockMethod", + rate: 1, + _id: "METHOD_ID", + enabled: true + } + ] +}; + const mockInvoice = Factory.OrderInvoice.makeOne({ currencyCode: "USD", // Need to ensure 0 discount to avoid creating negative totals @@ -54,13 +82,17 @@ beforeAll(async () => { testApp.registerPlugin({ name: "splitOrderItem.test.js", functionsByType: { - getFulfillmentMethodsWithQuotes: [getFulfillmentMethodsWithQuotes] + getFulfillmentMethodsWithQuotesShipping: [getFulfillmentMethodsWithQuotes] } }); await testApp.start(); shopId = await insertPrimaryShop(testApp.context); + // Add shipping methods + mockFulfillmentEntry.shopId = shopId; + await testApp.collections.Fulfillment.insertOne(mockFulfillmentEntry); + const adminGroup = Factory.Group.makeOne({ _id: "adminGroup", createdBy: null, @@ -131,6 +163,7 @@ test("user with `reaction:legacy:orders/move:item` permission can split an order ...mockShipmentMethod, currencyCode: "USD" }, + type: "shipping", shopId, totalItemQuantity: 3 }) diff --git a/apps/reaction/tests/integration/api/queries/getFlatRateFulfillmentRestriction/getFlatRateFulfillmentRestriction.test.js b/apps/reaction/tests/integration/api/queries/getFlatRateFulfillmentRestriction/getFlatRateFulfillmentRestriction.test.js index c685685a4c6..30e1e0db724 100644 --- a/apps/reaction/tests/integration/api/queries/getFlatRateFulfillmentRestriction/getFlatRateFulfillmentRestriction.test.js +++ b/apps/reaction/tests/integration/api/queries/getFlatRateFulfillmentRestriction/getFlatRateFulfillmentRestriction.test.js @@ -34,7 +34,7 @@ const adminGroup = Factory.Group.makeOne({ _id: "adminGroup", createdBy: null, name: "admin", - permissions: ["reaction:legacy:shippingRestrictions/read"], + permissions: ["reaction:legacy:fulfillmentRestrictions/read"], slug: "admin", shopId: internalShopId }); @@ -57,7 +57,7 @@ beforeAll(async () => { await testApp.start(); await insertPrimaryShop(testApp.context, { _id: internalShopId, name: shopName }); - await testApp.collections.FlatRateFulfillmentRestrictions.insertOne(mockFulfillmentRestriction); + await testApp.collections.FulfillmentRestrictions.insertOne(mockFulfillmentRestriction); await testApp.collections.Groups.insertOne(adminGroup); await testApp.createUserAndAccount(mockAdminAccount); getFlatRateFulfillmentRestriction = testApp.query(FlatRateFulfillmentRestrictionQuery); diff --git a/apps/reaction/tests/integration/api/queries/getFlatRateFulfillmentRestrictions/getFlatRateFulfillmentRestrictions.test.js b/apps/reaction/tests/integration/api/queries/getFlatRateFulfillmentRestrictions/getFlatRateFulfillmentRestrictions.test.js index 200b485ec46..0cb628755d0 100644 --- a/apps/reaction/tests/integration/api/queries/getFlatRateFulfillmentRestrictions/getFlatRateFulfillmentRestrictions.test.js +++ b/apps/reaction/tests/integration/api/queries/getFlatRateFulfillmentRestrictions/getFlatRateFulfillmentRestrictions.test.js @@ -32,7 +32,7 @@ const adminGroup = Factory.Group.makeOne({ _id: "adminGroup", createdBy: null, name: "admin", - permissions: ["reaction:legacy:shippingRestrictions/read"], + permissions: ["reaction:legacy:fulfillmentRestrictions/read"], slug: "admin", shopId: internalShopId }); @@ -54,7 +54,7 @@ beforeAll(async () => { await testApp.start(); await insertPrimaryShop(testApp.context, { _id: internalShopId, name: shopName }); - await testApp.collections.FlatRateFulfillmentRestrictions.insertOne(mockFulfillmentRestriction); + await testApp.collections.FulfillmentRestrictions.insertOne(mockFulfillmentRestriction); await testApp.collections.Groups.insertOne(adminGroup); await testApp.createUserAndAccount(mockAdminAccount); getFlatRateFulfillmentRestrictions = testApp.query(FlatRateFulfillmentRestrictionsQuery); diff --git a/apps/reaction/tests/integration/api/queries/shippingRates/flatRateFulfillmentMethod.test.js b/apps/reaction/tests/integration/api/queries/shippingRates/flatRateFulfillmentMethod.test.js index bfe559d70cb..dc96cd20935 100644 --- a/apps/reaction/tests/integration/api/queries/shippingRates/flatRateFulfillmentMethod.test.js +++ b/apps/reaction/tests/integration/api/queries/shippingRates/flatRateFulfillmentMethod.test.js @@ -41,7 +41,7 @@ const adminGroup = Factory.Group.makeOne({ _id: "adminGroup", createdBy: null, name: "admin", - permissions: ["reaction:legacy:shippingMethods/read"], + permissions: ["reaction:legacy:fulfillmentMethods/read"], slug: "admin", shopId: internalShopId }); @@ -84,7 +84,7 @@ beforeAll(async () => { await testApp.createUserAndAccount(mockCustomerAccount); await testApp.createUserAndAccount(mockAdminAccount); - await testApp.collections.Shipping.insertOne({ + await testApp.collections.Fulfillment.insertOne({ _id: "123", name: "Default Shipping Provider", shopId: internalShopId, diff --git a/apps/reaction/tests/integration/api/queries/shippingRates/flatRateFulfillmentMethods.test.js b/apps/reaction/tests/integration/api/queries/shippingRates/flatRateFulfillmentMethods.test.js index ac67008ba6e..cd9f7ff144c 100644 --- a/apps/reaction/tests/integration/api/queries/shippingRates/flatRateFulfillmentMethods.test.js +++ b/apps/reaction/tests/integration/api/queries/shippingRates/flatRateFulfillmentMethods.test.js @@ -27,6 +27,7 @@ for (let index = 10; index < 40; index += 1) { cost: index * 5, enabled: true, fulfillmentTypes: ["shipping"], + fulfillmentMethod: "flatRate", group }; @@ -37,7 +38,7 @@ const adminGroup = Factory.Group.makeOne({ _id: "adminGroup", createdBy: null, name: "admin", - permissions: ["reaction:legacy:shippingMethods/read"], + permissions: ["reaction:legacy:fulfillmentMethods/read"], slug: "admin", shopId: internalShopId }); @@ -83,9 +84,10 @@ beforeAll(async () => { await testApp.createUserAndAccount(mockCustomerAccount); await testApp.createUserAndAccount(mockAdminAccount); - await testApp.collections.Shipping.insertOne({ + await testApp.collections.Fulfillment.insertOne({ _id: "123", name: "Default Shipping Provider", + fulfillmentType: "shipping", shopId: internalShopId, provider: { enabled: true, diff --git a/apps/reaction/tests/util/factory.js b/apps/reaction/tests/util/factory.js index 9cc47e3607b..1f8f64ca382 100644 --- a/apps/reaction/tests/util/factory.js +++ b/apps/reaction/tests/util/factory.js @@ -16,6 +16,8 @@ import { CartAddress, CartInvoice, CartItem, + Shipment, + ShippingMethod, ShipmentQuote } from "@reactioncommerce/api-plugin-carts/src/simpleSchemas.js"; @@ -49,6 +51,7 @@ import { } from "@reactioncommerce/api-plugin-navigation/src/simpleSchemas.js"; import { + SelectedFulfillmentOption, CommonOrder, CommonOrderItem, extendOrdersSchemas, @@ -100,8 +103,20 @@ import { TaxRates } from "@reactioncommerce/api-plugin-taxes-flat-rate/src/simpleSchemas.js"; +import { + MethodEmptyData, + FulfillmentMethodSchema, + fulfillmentTypeSchema, + extendFulfillmentSchemas +} from "../../../../packages/api-plugin-fulfillment/src/simpleSchemas.js"; const schemasToAddToFactory = { + MethodEmptyData, + FulfillmentMethodSchema, + fulfillmentTypeSchema, + Shipment, + ShippingMethod, + SelectedFulfillmentOption, Account, AccountProfileAddress, AddressValidationRule, @@ -150,6 +165,7 @@ extendInventorySchemas(schemasToAddToFactory); extendSimplePricingSchemas(schemasToAddToFactory); extendTaxesSchemas(schemasToAddToFactory); extendOrdersSchemas(schemasToAddToFactory); +extendFulfillmentSchemas(schemasToAddToFactory, ["shipping", "mockType", "undecided"]); // Adds each to `Factory` object. For example, `Factory.Cart` // will be the factory that builds an object that matches the From 7314195f863901b14cff842cc47606baca3a376b Mon Sep 17 00:00:00 2001 From: Sujith Date: Thu, 3 Nov 2022 18:42:45 +0530 Subject: [PATCH 30/71] feat: switch from old shipments to fulfillment Signed-off-by: Sujith --- apps/reaction/package.json | 94 ++++++++++--------- apps/reaction/plugins.json | 8 +- .../src/util/defaultRoles.js | 12 +++ pnpm-lock.yaml | 40 -------- 4 files changed, 67 insertions(+), 87 deletions(-) diff --git a/apps/reaction/package.json b/apps/reaction/package.json index 34304e1b44a..abbfe3f5a89 100644 --- a/apps/reaction/package.json +++ b/apps/reaction/package.json @@ -20,51 +20,55 @@ "url": "https://github.com/reactioncommerce/reaction/issues" }, "dependencies": { - "@reactioncommerce/api-core": "2.0.1", - "@reactioncommerce/api-plugin-accounts": "2.0.4", - "@reactioncommerce/api-plugin-address-validation": "1.3.3", - "@reactioncommerce/api-plugin-address-validation-test": "1.0.3", - "@reactioncommerce/api-plugin-authentication": "2.2.5", - "@reactioncommerce/api-plugin-authorization-simple": "1.3.2", - "@reactioncommerce/api-plugin-carts": "1.3.5", - "@reactioncommerce/api-plugin-catalogs": "1.1.2", - "@reactioncommerce/api-plugin-discounts": "1.0.4", - "@reactioncommerce/api-plugin-discounts-codes": "1.2.4", - "@reactioncommerce/api-plugin-email": "1.1.5", - "@reactioncommerce/api-plugin-email-smtp": "1.0.8", - "@reactioncommerce/api-plugin-email-templates": "1.1.7", - "@reactioncommerce/api-plugin-files": "1.0.19", - "@reactioncommerce/api-plugin-i18n": "1.0.6", - "@reactioncommerce/api-plugin-inventory": "1.0.5", - "@reactioncommerce/api-plugin-inventory-simple": "1.0.6", - "@reactioncommerce/api-plugin-job-queue": "1.0.7", - "@reactioncommerce/api-plugin-navigation": "1.1.2", - "@reactioncommerce/api-plugin-notifications": "1.1.4", - "@reactioncommerce/api-plugin-orders": "1.4.5", - "@reactioncommerce/api-plugin-payments": "1.0.5", - "@reactioncommerce/api-plugin-payments-example": "1.1.2", - "@reactioncommerce/api-plugin-payments-stripe-sca": "1.0.2", - "@reactioncommerce/api-plugin-pricing-simple": "1.0.7", - "@reactioncommerce/api-plugin-products": "1.3.1", - "@reactioncommerce/api-plugin-settings": "1.0.7", - "@reactioncommerce/api-plugin-shipments": "1.0.3", - "@reactioncommerce/api-plugin-shipments-flat-rate": "1.0.10", - "@reactioncommerce/api-plugin-shops": "1.3.0", - "@reactioncommerce/api-plugin-simple-schema": "1.0.3", - "@reactioncommerce/api-plugin-sitemap-generator": "1.2.3", - "@reactioncommerce/api-plugin-surcharges": "1.1.8", - "@reactioncommerce/api-plugin-system-information": "1.1.4", - "@reactioncommerce/api-plugin-tags": "1.2.0", - "@reactioncommerce/api-plugin-taxes": "1.1.3", - "@reactioncommerce/api-plugin-taxes-flat-rate": "1.0.8", - "@reactioncommerce/api-plugin-translations": "1.2.2", - "@reactioncommerce/api-utils": "1.17.0", - "@reactioncommerce/db-version-check": "1.0.0", - "@reactioncommerce/file-collections": "0.9.3", - "@reactioncommerce/file-collections-sa-gridfs": "0.1.5", - "@reactioncommerce/logger": "1.1.5", - "@reactioncommerce/nodemailer": "5.0.5", - "@reactioncommerce/random": "1.0.2", + "@reactioncommerce/api-core": "^2.0.0", + "@reactioncommerce/api-plugin-accounts": "^2.0.0", + "@reactioncommerce/api-plugin-address-validation": "^1.3.1", + "@reactioncommerce/api-plugin-address-validation-test": "^1.0.3", + "@reactioncommerce/api-plugin-authentication": "^2.2.3", + "@reactioncommerce/api-plugin-authorization-simple": "^1.3.1", + "@reactioncommerce/api-plugin-carts": "^1.2.4", + "@reactioncommerce/api-plugin-catalogs": "^1.1.2", + "@reactioncommerce/api-plugin-discounts": "^1.0.1", + "@reactioncommerce/api-plugin-discounts-codes": "^1.2.0", + "@reactioncommerce/api-plugin-email": "^1.1.0", + "@reactioncommerce/api-plugin-email-smtp": "^1.0.5", + "@reactioncommerce/api-plugin-email-templates": "^1.1.1", + "@reactioncommerce/api-plugin-files": "^1.0.19", + "@reactioncommerce/api-plugin-i18n": "^1.0.4", + "@reactioncommerce/api-plugin-inventory": "^1.0.3", + "@reactioncommerce/api-plugin-inventory-simple": "^1.0.4", + "@reactioncommerce/api-plugin-job-queue": "^1.0.2", + "@reactioncommerce/api-plugin-navigation": "^1.1.1", + "@reactioncommerce/api-plugin-notifications": "^1.1.0", + "@reactioncommerce/api-plugin-orders": "^1.4.2", + "@reactioncommerce/api-plugin-payments": "^1.0.1", + "@reactioncommerce/api-plugin-payments-example": "^1.1.1", + "@reactioncommerce/api-plugin-payments-stripe-sca": "^1.0.2", + "@reactioncommerce/api-plugin-pricing-simple": "^1.0.3", + "@reactioncommerce/api-plugin-products": "^1.0.2", + "@reactioncommerce/api-plugin-settings": "^1.0.2", + "@reactioncommerce/api-plugin-fulfillment": "^1.0.0", + "@reactioncommerce/api-plugin-fulfillment-type-shipping": "^1.0.0", + "@reactioncommerce/api-plugin-fulfillment-method-shipping-flat-rate": "^1.0.0", + "@reactioncommerce/api-plugin-fulfillment-method-shipping-ups": "^1.0.0", + "@reactioncommerce/api-plugin-fulfillment-type-pickup": "^1.0.0", + "@reactioncommerce/api-plugin-fulfillment-method-pickup-store": "^1.0.0", + "@reactioncommerce/api-plugin-shops": "^1.2.1", + "@reactioncommerce/api-plugin-simple-schema": "^1.0.3", + "@reactioncommerce/api-plugin-sitemap-generator": "^1.2.2", + "@reactioncommerce/api-plugin-surcharges": "^1.1.7", + "@reactioncommerce/api-plugin-system-information": "^1.1.3", + "@reactioncommerce/api-plugin-tags": "^1.1.1", + "@reactioncommerce/api-plugin-taxes": "^1.1.1", + "@reactioncommerce/api-plugin-taxes-flat-rate": "^1.0.4", + "@reactioncommerce/api-plugin-translations": "^1.2.0", + "@reactioncommerce/api-utils": "^1.16.5", + "@reactioncommerce/db-version-check": "^1.0.0", + "@reactioncommerce/file-collections": "^0.9.3", + "@reactioncommerce/file-collections-sa-gridfs": "^0.1.5", + "@reactioncommerce/logger": "^1.1.3", + "@reactioncommerce/nodemailer": "^5.0.5", + "@reactioncommerce/random": "^1.0.2", "@snyk/protect": "latest", "graphql": "~14.7.0", "semver": "~6.3.0", diff --git a/apps/reaction/plugins.json b/apps/reaction/plugins.json index 429aa5fd64e..6cac68d4dd1 100644 --- a/apps/reaction/plugins.json +++ b/apps/reaction/plugins.json @@ -28,8 +28,12 @@ "discounts": "@reactioncommerce/api-plugin-discounts", "discountCodes": "@reactioncommerce/api-plugin-discounts-codes", "surcharges": "@reactioncommerce/api-plugin-surcharges", - "shipments": "@reactioncommerce/api-plugin-shipments", - "shipmentsFlatRate": "@reactioncommerce/api-plugin-shipments-flat-rate", + "fulfillment": "@reactioncommerce/api-plugin-fulfillment", + "fulfillmentTypeShipping": "@reactioncommerce/api-plugin-fulfillment-type-shipping", + "fulfillmentMethodShippingFlatRate": "@reactioncommerce/api-plugin-fulfillment-method-shipping-flat-rate", + "fulfillmentMethodShippingUPS": "@reactioncommerce/api-plugin-fulfillment-method-shipping-ups", + "fulfillmentTypePickup": "@reactioncommerce/api-plugin-fulfillment-type-pickup", + "fulfillmentMethodPickupStore": "@reactioncommerce/api-plugin-fulfillment-method-pickup-store", "taxes": "@reactioncommerce/api-plugin-taxes", "taxesFlatRate": "@reactioncommerce/api-plugin-taxes-flat-rate", "navigation": "@reactioncommerce/api-plugin-navigation", diff --git a/packages/api-plugin-authorization-simple/src/util/defaultRoles.js b/packages/api-plugin-authorization-simple/src/util/defaultRoles.js index 770cd1deb61..998b06a5051 100644 --- a/packages/api-plugin-authorization-simple/src/util/defaultRoles.js +++ b/packages/api-plugin-authorization-simple/src/util/defaultRoles.js @@ -60,6 +60,18 @@ export const defaultShopManagerRoles = [ "reaction:legacy:products/update:prices", "reaction:legacy:products/update", "reaction:legacy:shipping-rates/update:settings", + "reaction:legacy:fulfillmentTypes/create", + "reaction:legacy:fulfillmentTypes/delete", + "reaction:legacy:fulfillmentTypes/read", + "reaction:legacy:fulfillmentTypes/update", + "reaction:legacy:fulfillmentMethods/create", + "reaction:legacy:fulfillmentMethods/delete", + "reaction:legacy:fulfillmentMethods/read", + "reaction:legacy:fulfillmentMethods/update", + "reaction:legacy:fulfillmentRestrictions/create", + "reaction:legacy:fulfillmentRestrictions/delete", + "reaction:legacy:fulfillmentRestrictions/read", + "reaction:legacy:fulfillmentRestrictions/update", "reaction:legacy:shippingMethods/create", "reaction:legacy:shippingMethods/delete", "reaction:legacy:shippingMethods/read", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a54914d1c58..3cc712c27da 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -694,46 +694,6 @@ importers: '@reactioncommerce/api-plugin-carts': link:../api-plugin-carts '@reactioncommerce/data-factory': 1.0.1 - packages/api-plugin-fulfillment-method-shipping-flat-rate: - specifiers: - '@reactioncommerce/api-utils': ^1.16.9 - '@reactioncommerce/logger': ^1.1.3 - '@reactioncommerce/random': ~1.0.2 - '@reactioncommerce/reaction-error': ^1.0.1 - simpl-schema: ^1.12.2 - dependencies: - '@reactioncommerce/api-utils': link:../api-utils - '@reactioncommerce/logger': link:../logger - '@reactioncommerce/random': link:../random - '@reactioncommerce/reaction-error': link:../reaction-error - simpl-schema: 1.12.3 - - packages/api-plugin-fulfillment-method-shipping-ups: - specifiers: - '@reactioncommerce/api-utils': ^1.16.9 - '@reactioncommerce/reaction-error': ^1.0.1 - simpl-schema: ^1.12.2 - dependencies: - '@reactioncommerce/api-utils': link:../api-utils - '@reactioncommerce/reaction-error': link:../reaction-error - simpl-schema: 1.12.3 - - packages/api-plugin-fulfillment-type-shipping: - specifiers: - '@reactioncommerce/api-utils': ^1.16.9 - '@reactioncommerce/logger': ^1.1.3 - '@reactioncommerce/random': ~1.0.2 - '@reactioncommerce/reaction-error': ^1.0.1 - lodash: ^4.17.21 - simpl-schema: ^1.12.2 - dependencies: - '@reactioncommerce/api-utils': link:../api-utils - '@reactioncommerce/logger': link:../logger - '@reactioncommerce/random': link:../random - '@reactioncommerce/reaction-error': link:../reaction-error - lodash: 4.17.21 - simpl-schema: 1.12.3 - packages/api-plugin-i18n: specifiers: '@reactioncommerce/api-utils': ^1.16.5 From b047c037925ca42707011f41f1ee08fc9183a63b Mon Sep 17 00:00:00 2001 From: Sujith Date: Tue, 25 Oct 2022 00:55:29 +0530 Subject: [PATCH 31/71] feat: test setFulfillmentTypeForItems Signed-off-by: Sujith --- .../setFulfillmentTypeForItems.test.js | 123 ++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 packages/api-plugin-carts/src/mutations/setFulfillmentTypeForItems.test.js diff --git a/packages/api-plugin-carts/src/mutations/setFulfillmentTypeForItems.test.js b/packages/api-plugin-carts/src/mutations/setFulfillmentTypeForItems.test.js new file mode 100644 index 00000000000..43420c8700d --- /dev/null +++ b/packages/api-plugin-carts/src/mutations/setFulfillmentTypeForItems.test.js @@ -0,0 +1,123 @@ +import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; +import ReactionError from "@reactioncommerce/reaction-error"; +import setFulfillmentTypeForItems from "./setFulfillmentTypeForItems.js"; + +const dbCart = { + _id: "cartId", + items: [ + { + _id: "cartItemId1", + quantity: 5, + price: { + amount: 400, + currencyCode: "mockCurrencyCode" + }, + subtotal: { + amount: 2000, + currencyCode: "mockCurrencyCode" + } + }, + { + _id: "cartItemId2", + quantity: 5, + price: { + amount: 200, + currencyCode: "mockCurrencyCode" + }, + subtotal: { + amount: 1000, + currencyCode: "mockCurrencyCode" + } + } + ] +}; + +const cartToken = "TOKEN"; +const hashedToken = "+YED6SF/CZIIVp0pXBsnbxghNIY2wmjIVLsqCG4AN80="; + +beforeAll(() => { + if (!mockContext.mutations.saveCart) { + mockContext.mutations.saveCart = jest.fn().mockName("context.mutations.saveCart").mockImplementation(async (_, cart) => cart); + } +}); + +test("throws if cart to be updated not found", async () => { + mockContext.collections.Cart.findOne.mockReturnValueOnce(Promise.resolve(undefined)); + const expectedError = new ReactionError("not-found", "Cart not found"); + await expect(setFulfillmentTypeForItems(mockContext, { + cartId: "cartId", + cartToken, + fulfillmentType: "shipping", + items: ["cartItemId"] + })).rejects.toThrow(expectedError); +}); + +test("throws if invalid fulfillment type provided", async () => { + mockContext.collections.Cart.findOne.mockReturnValueOnce(Promise.resolve(dbCart)); + const expectedError = new ReactionError("invalid-param", "Invalid Fulfillment Type received"); + await expect(setFulfillmentTypeForItems(mockContext, { + cartId: "cartId", + cartToken, + fulfillmentType: "undecided", + items: ["cartItemId"] + })).rejects.toThrow(expectedError); +}); + +test("throws if invalid fulfillment type provided", async () => { + mockContext.collections.Cart.findOne.mockReturnValueOnce(Promise.resolve(dbCart)); + const expectedError = new ReactionError("invalid-param", "Items not provided"); + await expect(setFulfillmentTypeForItems(mockContext, { + cartId: "cartId", + cartToken, + fulfillmentType: "shipping", + items: [] + })).rejects.toThrow(expectedError); +}); + +test("sets the selected fulfillment type for each input group in the cart", async () => { + mockContext.collections.Cart.findOne.mockReturnValueOnce(Promise.resolve(dbCart)); + + const result = await setFulfillmentTypeForItems(mockContext, { + cartId: "cartId", + cartToken, + fulfillmentType: "shipping", + items: ["cartItemId2"] + }); + + const expected = { + _id: "cartId", + items: [ + { + _id: "cartItemId1", + quantity: 5, + price: { + amount: 400, + currencyCode: "mockCurrencyCode" + }, + subtotal: { + amount: 2000, + currencyCode: "mockCurrencyCode" + } + }, + { + _id: "cartItemId2", + selectedFulfillmentType: "shipping", + quantity: 5, + price: { + amount: 200, + currencyCode: "mockCurrencyCode" + }, + subtotal: { + amount: 1000, + currencyCode: "mockCurrencyCode" + } + } + ] + }; + expect(mockContext.collections.Cart.findOne).toHaveBeenCalledWith({ + _id: "cartId", + anonymousAccessToken: hashedToken + }); + + expect(result).toEqual({ cart: expected }); +}); From 0ddcaa4f6eca08d084e0835ec117291c3ce5c7f8 Mon Sep 17 00:00:00 2001 From: Sujith Date: Sat, 29 Oct 2022 11:45:42 +0530 Subject: [PATCH 32/71] fix: package and plugin.json update Signed-off-by: Sujith --- apps/reaction/package.json | 12 ++++++------ apps/reaction/plugins.json | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/reaction/package.json b/apps/reaction/package.json index abbfe3f5a89..4c5ef86aa87 100644 --- a/apps/reaction/package.json +++ b/apps/reaction/package.json @@ -47,12 +47,12 @@ "@reactioncommerce/api-plugin-pricing-simple": "^1.0.3", "@reactioncommerce/api-plugin-products": "^1.0.2", "@reactioncommerce/api-plugin-settings": "^1.0.2", - "@reactioncommerce/api-plugin-fulfillment": "^1.0.0", - "@reactioncommerce/api-plugin-fulfillment-type-shipping": "^1.0.0", - "@reactioncommerce/api-plugin-fulfillment-method-shipping-flat-rate": "^1.0.0", - "@reactioncommerce/api-plugin-fulfillment-method-shipping-ups": "^1.0.0", - "@reactioncommerce/api-plugin-fulfillment-type-pickup": "^1.0.0", - "@reactioncommerce/api-plugin-fulfillment-method-pickup-store": "^1.0.0", + "@reactioncommerce/api-plugin-fulfillment": "1.0.0", + "@reactioncommerce/api-plugin-fulfillment-type-shipping": "1.0.0", + "@reactioncommerce/api-plugin-fulfillment-method-shipping-flat-rate": "1.0.0", + "@reactioncommerce/api-plugin-fulfillment-method-shipping-dynamic-rate": "1.0.0", + "@reactioncommerce/api-plugin-fulfillment-type-pickup": "1.0.0", + "@reactioncommerce/api-plugin-fulfillment-method-pickup-store": "1.0.0", "@reactioncommerce/api-plugin-shops": "^1.2.1", "@reactioncommerce/api-plugin-simple-schema": "^1.0.3", "@reactioncommerce/api-plugin-sitemap-generator": "^1.2.2", diff --git a/apps/reaction/plugins.json b/apps/reaction/plugins.json index 6cac68d4dd1..9d074892bc7 100644 --- a/apps/reaction/plugins.json +++ b/apps/reaction/plugins.json @@ -31,7 +31,7 @@ "fulfillment": "@reactioncommerce/api-plugin-fulfillment", "fulfillmentTypeShipping": "@reactioncommerce/api-plugin-fulfillment-type-shipping", "fulfillmentMethodShippingFlatRate": "@reactioncommerce/api-plugin-fulfillment-method-shipping-flat-rate", - "fulfillmentMethodShippingUPS": "@reactioncommerce/api-plugin-fulfillment-method-shipping-ups", + "fulfillmentMethodShippingDynamicRate": "@reactioncommerce/api-plugin-fulfillment-method-shipping-dynamic-rate", "fulfillmentTypePickup": "@reactioncommerce/api-plugin-fulfillment-type-pickup", "fulfillmentMethodPickupStore": "@reactioncommerce/api-plugin-fulfillment-method-pickup-store", "taxes": "@reactioncommerce/api-plugin-taxes", From d88a878b5ecd4c15562fe3db0f7e8ea4aade650d Mon Sep 17 00:00:00 2001 From: Sujith Date: Thu, 3 Nov 2022 18:58:06 +0530 Subject: [PATCH 33/71] fix: review comment combine destructure Signed-off-by: Sujith --- .../api-plugin-carts/src/util/updateCartFulfillmentGroups.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/api-plugin-carts/src/util/updateCartFulfillmentGroups.js b/packages/api-plugin-carts/src/util/updateCartFulfillmentGroups.js index 5b8fb5da6ff..2f902503330 100644 --- a/packages/api-plugin-carts/src/util/updateCartFulfillmentGroups.js +++ b/packages/api-plugin-carts/src/util/updateCartFulfillmentGroups.js @@ -77,8 +77,7 @@ export default async function updateCartFulfillmentGroups(context, cart) { // Every time the cart is updated, create any missing fulfillment groups as necessary. // We need one group per type per shop, containing only the items from that shop. // Also make sure that every item is assigned to a fulfillment group. - const { collections } = context; - const { Fulfillment } = collections; + const { collections: { Fulfillment } } = context; const currentGroups = cart.shipping || []; for (const item of (cart.items || [])) { From 1cdfafe2cb3a4949640dc5af7a023ca54163e491 Mon Sep 17 00:00:00 2001 From: Sujith Date: Thu, 20 Oct 2022 21:29:59 +0530 Subject: [PATCH 34/71] feat: new validateOrder and placeOrder refactor Signed-off-by: Sujith --- .../src/mutations/placeOrder.js | 275 +-------------- .../api-plugin-orders/src/queries/index.js | 2 + .../src/queries/validateOrder.js | 16 + .../src/resolvers/Query/index.js | 2 + .../src/resolvers/Query/validateOrder.js | 48 +++ .../src/schemas/schema.graphql | 37 +++ .../src/util/addInvoiceToGroup.js | 2 +- .../src/util/addShipmentMethodToGroup.js | 13 +- .../util/orderValidators/createPayments.js | 110 ++++++ .../util/orderValidators/getCustomFields.js | 25 ++ .../src/util/orderValidators/getDiscounts.js | 17 + .../getFinalFulfillmentGroups.js | 147 ++++++++ .../util/orderValidators/getReferenceId.js | 39 +++ .../src/util/orderValidators/prepareOrder.js | 314 ++++++++++++++++++ .../validateInitialOrderData.js | 34 ++ .../src/util/updateGroupTotals.js | 4 +- 16 files changed, 808 insertions(+), 277 deletions(-) create mode 100644 packages/api-plugin-orders/src/queries/validateOrder.js create mode 100644 packages/api-plugin-orders/src/resolvers/Query/validateOrder.js create mode 100644 packages/api-plugin-orders/src/util/orderValidators/createPayments.js create mode 100644 packages/api-plugin-orders/src/util/orderValidators/getCustomFields.js create mode 100644 packages/api-plugin-orders/src/util/orderValidators/getDiscounts.js create mode 100644 packages/api-plugin-orders/src/util/orderValidators/getFinalFulfillmentGroups.js create mode 100644 packages/api-plugin-orders/src/util/orderValidators/getReferenceId.js create mode 100644 packages/api-plugin-orders/src/util/orderValidators/prepareOrder.js create mode 100644 packages/api-plugin-orders/src/util/orderValidators/validateInitialOrderData.js diff --git a/packages/api-plugin-orders/src/mutations/placeOrder.js b/packages/api-plugin-orders/src/mutations/placeOrder.js index 8acd0f43305..de858d959e8 100644 --- a/packages/api-plugin-orders/src/mutations/placeOrder.js +++ b/packages/api-plugin-orders/src/mutations/placeOrder.js @@ -1,108 +1,4 @@ -import _ from "lodash"; -import SimpleSchema from "simpl-schema"; -import Logger from "@reactioncommerce/logger"; -import Random from "@reactioncommerce/random"; -import ReactionError from "@reactioncommerce/reaction-error"; -import getAnonymousAccessToken from "@reactioncommerce/api-utils/getAnonymousAccessToken.js"; -import buildOrderFulfillmentGroupFromInput from "../util/buildOrderFulfillmentGroupFromInput.js"; -import verifyPaymentsMatchOrderTotal from "../util/verifyPaymentsMatchOrderTotal.js"; -import { Order as OrderSchema, orderInputSchema, Payment as PaymentSchema, paymentInputSchema } from "../simpleSchemas.js"; - -const inputSchema = new SimpleSchema({ - "order": orderInputSchema, - "payments": { - type: Array, - optional: true - }, - "payments.$": paymentInputSchema -}); - -/** - * @summary Create all authorized payments for a potential order - * @param {String} [accountId] The ID of the account placing the order - * @param {Object} [billingAddress] Billing address for the order as a whole - * @param {Object} context - The application context - * @param {String} currencyCode Currency code for interpreting the amount of all payments - * @param {String} email Email address for the order - * @param {Number} orderTotal Total due for the order - * @param {Object[]} paymentsInput List of payment inputs - * @param {Object} [shippingAddress] Shipping address, if relevant, for fraud detection - * @param {String} shop shop that owns the order - * @returns {Object[]} Array of created payments - */ -async function createPayments({ - accountId, - billingAddress, - context, - currencyCode, - email, - orderTotal, - paymentsInput, - shippingAddress, - shop -}) { - // Determining which payment methods are enabled for the shop - const availablePaymentMethods = shop.availablePaymentMethods || []; - - // Verify that total of payment inputs equals total due. We need to be sure - // to do this before creating any payment authorizations - verifyPaymentsMatchOrderTotal(paymentsInput || [], orderTotal); - - // Create authorized payments for each - const paymentPromises = (paymentsInput || []).map(async (paymentInput) => { - const { amount, method: methodName } = paymentInput; - - // Verify that this payment method is enabled for the shop - if (!availablePaymentMethods.includes(methodName)) { - throw new ReactionError("payment-failed", `Payment method not enabled for this shop: ${methodName}`); - } - - // Grab config for this payment method - let paymentMethodConfig; - try { - paymentMethodConfig = context.queries.getPaymentMethodConfigByName(methodName); - } catch (error) { - Logger.error(error); - throw new ReactionError("payment-failed", `Invalid payment method name: ${methodName}`); - } - - // Authorize this payment - const payment = await paymentMethodConfig.functions.createAuthorizedPayment(context, { - accountId, // optional - amount, - billingAddress: paymentInput.billingAddress || billingAddress, - currencyCode, - email, - shippingAddress, // optional, for fraud detection, the first shipping address if shipping to multiple - shopId: shop._id, - paymentData: { - ...(paymentInput.data || {}) - } // optional, object, blackbox - }); - - const paymentWithCurrency = { - ...payment, - // This is from previous support for exchange rates, which was removed in v3.0.0 - currency: { exchangeRate: 1, userCurrency: currencyCode }, - currencyCode - }; - - PaymentSchema.validate(paymentWithCurrency); - - return paymentWithCurrency; - }); - - let payments; - try { - payments = await Promise.all(paymentPromises); - payments = payments.filter((payment) => !!payment); // remove nulls - } catch (error) { - Logger.error("createOrder: error creating payments", error.message); - throw new ReactionError("payment-failed", `There was a problem authorizing this payment: ${error.message}`); - } - - return payments; -} +import prepareOrder from "../util/orderValidators/prepareOrder.js"; /** * @method placeOrder @@ -112,173 +8,10 @@ async function createPayments({ * @returns {Promise} Object with `order` property containing the created order */ export default async function placeOrder(context, input) { - const cleanedInput = inputSchema.clean(input); // add default values and such - inputSchema.validate(cleanedInput); - - const { order: orderInput, payments: paymentsInput } = cleanedInput; - const { - billingAddress, - cartId, - currencyCode, - customFields: customFieldsFromClient, - email, - fulfillmentGroups, - ordererPreferredLanguage, - shopId - } = orderInput; - const { accountId, appEvents, collections, getFunctionsOfType, userId } = context; - const { Orders, Cart } = collections; - - const shop = await context.queries.shopById(context, shopId); - if (!shop) throw new ReactionError("not-found", "Shop not found"); - - if (!userId && !shop.allowGuestCheckout) { - throw new ReactionError("access-denied", "Guest checkout not allowed"); - } - - let cart; - if (cartId) { - cart = await Cart.findOne({ _id: cartId }); - if (!cart) { - throw new ReactionError("not-found", "Cart not found while trying to place order"); - } - } - - - // We are mixing concerns a bit here for now. This is for backwards compatibility with current - // discount codes feature. We are planning to revamp discounts soon, but until then, we'll look up - // any discounts on the related cart here. - let discounts = []; - let discountTotal = 0; - if (cart) { - const discountsResult = await context.queries.getDiscountsTotalForCart(context, cart); - ({ discounts } = discountsResult); - discountTotal = discountsResult.total; - } - - // Create array for surcharges to apply to order, if applicable - // Array is populated inside `fulfillmentGroups.map()` - const orderSurcharges = []; - - // Create orderId - const orderId = Random.id(); - - - // Add more props to each fulfillment group, and validate/build the items in each group - let orderTotal = 0; - let shippingAddressForPayments = null; - const finalFulfillmentGroups = await Promise.all(fulfillmentGroups.map(async (inputGroup) => { - const { group, groupSurcharges } = await buildOrderFulfillmentGroupFromInput(context, { - accountId, - billingAddress, - cartId, - currencyCode, - discountTotal, - inputGroup, - orderId, - cart - }); - - // We save off the first shipping address found, for passing to payment services. They use this - // for fraud detection. - if (group.address && !shippingAddressForPayments) shippingAddressForPayments = group.address; - - // Push all group surcharges to overall order surcharge array. - // Currently, we do not save surcharges per group - orderSurcharges.push(...groupSurcharges); - - // Add the group total to the order total - orderTotal += group.invoice.total; - - return group; - })); - - const payments = await createPayments({ - accountId, - billingAddress, - context, - currencyCode, - email, - orderTotal, - paymentsInput, - shippingAddress: shippingAddressForPayments, - shop - }); - - // Create anonymousAccessToken if no account ID - const fullToken = accountId ? null : getAnonymousAccessToken(); - - const now = new Date(); - - const order = { - _id: orderId, - accountId, - billingAddress, - cartId, - createdAt: now, - currencyCode, - discounts, - email, - ordererPreferredLanguage: ordererPreferredLanguage || null, - payments, - shipping: finalFulfillmentGroups, - shopId, - surcharges: orderSurcharges, - totalItemQuantity: finalFulfillmentGroups.reduce((sum, group) => sum + group.totalItemQuantity, 0), - updatedAt: now, - workflow: { - status: "new", - workflow: ["new"] - } - }; - - if (fullToken) { - const dbToken = { ...fullToken }; - // don't store the raw token in db, only the hash - delete dbToken.token; - order.anonymousAccessTokens = [dbToken]; - } - - let referenceId; - const createReferenceIdFunctions = getFunctionsOfType("createOrderReferenceId"); - if (!createReferenceIdFunctions || createReferenceIdFunctions.length === 0) { - // if the cart has a reference Id, and no custom function is created use that - if (_.get(cart, "referenceId")) { // we want the else to fallthrough if no cart to keep the if/else logic simple - ({ referenceId } = cart); - } else { - referenceId = Random.id(); - } - } else { - referenceId = await createReferenceIdFunctions[0](context, order, cart); - if (typeof referenceId !== "string") { - throw new ReactionError("invalid-parameter", "createOrderReferenceId function returned a non-string value"); - } - if (createReferenceIdFunctions.length > 1) { - Logger.warn("More than one createOrderReferenceId function defined. Using first one defined"); - } - } - - order.referenceId = referenceId; - - - // Apply custom order data transformations from plugins - const transformCustomOrderFieldsFuncs = getFunctionsOfType("transformCustomOrderFields"); - if (transformCustomOrderFieldsFuncs.length > 0) { - let customFields = { ...(customFieldsFromClient || {}) }; - // We need to run each of these functions in a series, rather than in parallel, because - // each function expects to get the result of the previous. It is recommended to disable `no-await-in-loop` - // eslint rules when the output of one iteration might be used as input in another iteration, such as this case here. - // See https://eslint.org/docs/rules/no-await-in-loop#when-not-to-use-it - for (const transformCustomOrderFieldsFunc of transformCustomOrderFieldsFuncs) { - customFields = await transformCustomOrderFieldsFunc({ context, customFields, order }); // eslint-disable-line no-await-in-loop - } - order.customFields = customFields; - } else { - order.customFields = customFieldsFromClient; - } + const { appEvents, collections, userId } = context; + const { Orders } = collections; - // Validate and save - OrderSchema.validate(order); + const { order, fullToken } = await prepareOrder(context, input, "createOrderObject"); await Orders.insertOne(order); await appEvents.emit("afterOrderCreate", { createdBy: userId, order }); diff --git a/packages/api-plugin-orders/src/queries/index.js b/packages/api-plugin-orders/src/queries/index.js index d86caab24bb..dd7184d562b 100644 --- a/packages/api-plugin-orders/src/queries/index.js +++ b/packages/api-plugin-orders/src/queries/index.js @@ -4,8 +4,10 @@ import orders from "./orders.js"; import ordersByAccountId from "./ordersByAccountId.js"; import refunds from "./refunds.js"; import refundsByPaymentId from "./refundsByPaymentId.js"; +import validateOrder from "./validateOrder.js"; export default { + validateOrder, orderById, orderByReferenceId, orders, diff --git a/packages/api-plugin-orders/src/queries/validateOrder.js b/packages/api-plugin-orders/src/queries/validateOrder.js new file mode 100644 index 00000000000..88478a69267 --- /dev/null +++ b/packages/api-plugin-orders/src/queries/validateOrder.js @@ -0,0 +1,16 @@ +import prepareOrder from "../util/orderValidators/prepareOrder.js"; + +/** + * @name validateOrder + * @method + * @memberof Order/NoMeteorQueries + * @summary Validates if the input order details is valid and ready for order processing + * @param {Object} context - an object containing the per-request state + * @param {Object} input - order details, refer inputSchema + * @returns {Promise} output - validation results + */ +export default async function validateOrder(context, input) { + const { errors, success } = await prepareOrder(context, input, "validateOrder"); + const output = { errors, success }; + return output; +} diff --git a/packages/api-plugin-orders/src/resolvers/Query/index.js b/packages/api-plugin-orders/src/resolvers/Query/index.js index d86caab24bb..dd7184d562b 100644 --- a/packages/api-plugin-orders/src/resolvers/Query/index.js +++ b/packages/api-plugin-orders/src/resolvers/Query/index.js @@ -4,8 +4,10 @@ import orders from "./orders.js"; import ordersByAccountId from "./ordersByAccountId.js"; import refunds from "./refunds.js"; import refundsByPaymentId from "./refundsByPaymentId.js"; +import validateOrder from "./validateOrder.js"; export default { + validateOrder, orderById, orderByReferenceId, orders, diff --git a/packages/api-plugin-orders/src/resolvers/Query/validateOrder.js b/packages/api-plugin-orders/src/resolvers/Query/validateOrder.js new file mode 100644 index 00000000000..4fec5bdbbbb --- /dev/null +++ b/packages/api-plugin-orders/src/resolvers/Query/validateOrder.js @@ -0,0 +1,48 @@ +import { + decodeCartOpaqueId, + decodeFulfillmentMethodOpaqueId, + decodeOrderItemsOpaqueIds, + decodeShopOpaqueId +} from "../../xforms/id.js"; + +/** + * @name Query.validateOrder + * @method + * @memberof Order/GraphQL + * @summary Validate if the order is ready + * @param {Object} parentResult - unused + * @param {Object} args.input - an object of all mutation arguments that were sent by the client + * @param {Object} args.input.order - The order input + * @param {Object[]} args.input.payments - Payment info + * @param {Object} context - an object containing the per-request state + * @returns {Promise} A validation result object + */ +export default async function validateOrder(parentResult, { input }, context) { + const { order, payments } = input; + const { cartId: opaqueCartId, fulfillmentGroups, shopId: opaqueShopId } = order; + + const cartId = opaqueCartId ? decodeCartOpaqueId(opaqueCartId) : null; + const shopId = decodeShopOpaqueId(opaqueShopId); + + const transformedFulfillmentGroups = fulfillmentGroups.map((group) => ({ + ...group, + items: decodeOrderItemsOpaqueIds(group.items), + selectedFulfillmentMethodId: decodeFulfillmentMethodOpaqueId(group.selectedFulfillmentMethodId), + shopId: decodeShopOpaqueId(group.shopId) + })); + + const { errors, success } = await context.queries.validateOrder( + context, + { + order: { + ...order, + cartId, + fulfillmentGroups: transformedFulfillmentGroups, + shopId + }, + payments + } + ); + + return { errors, success }; +} diff --git a/packages/api-plugin-orders/src/schemas/schema.graphql b/packages/api-plugin-orders/src/schemas/schema.graphql index c0d935743ef..a909d9f21b1 100644 --- a/packages/api-plugin-orders/src/schemas/schema.graphql +++ b/packages/api-plugin-orders/src/schemas/schema.graphql @@ -112,6 +112,12 @@ extend type Query { "A valid anonymous access token for this order. Required if the order is not linked with an account." token: String ): [Refund] + + "Validate the order for correctness & completeness before placing the order" + validateOrder( + "input with order and payment objects similar to the one used for placeOrder" + input: validateOrderInput! + ): validateOrderPayload! } extend type Mutation { @@ -954,6 +960,37 @@ type MoveOrderItemsPayload { order: Order! } +"Input for the validateOrder query" +input validateOrderInput { + "The order to be validated and created, if payment is accepted" + order: OrderInput! + + """ + The information necessary to pay. Collect this information from the shopper during a checkout flow. + You need not provide any payment input if the total is zero. + The total of all payment input `amount` fields must add up to the order total. The first payment + method where the `amount` field is `null` will be charged the remainder due. + """ + payments: [PaymentInput] +} + +type ValidationErrors { + errorName: String, + errorType: String, + errorField: String, + fieldValue: String, + errorMessage: String +} + +"Response payload for the validateOrder query" +type validateOrderPayload { + "Orders that were created" + errors: [ValidationErrors] + + "Validation Result" + success: Boolean +} + "Response payload for the placeOrder mutation" type PlaceOrderPayload { "The same string you sent with the mutation params, for matching mutation calls with their responses" diff --git a/packages/api-plugin-orders/src/util/addInvoiceToGroup.js b/packages/api-plugin-orders/src/util/addInvoiceToGroup.js index 8bccce940eb..46c9f5a665b 100644 --- a/packages/api-plugin-orders/src/util/addInvoiceToGroup.js +++ b/packages/api-plugin-orders/src/util/addInvoiceToGroup.js @@ -35,7 +35,7 @@ export default function addInvoiceToGroup({ // `buildOrderInputFromCart.js` in the client code. const total = +accounting.toFixed(Math.max(0, itemTotal + fulfillmentTotal + taxTotal + groupSurchargeTotal - groupDiscountTotal), 3); - group.invoice = { + return { currencyCode, discounts: groupDiscountTotal, effectiveTaxRate, diff --git a/packages/api-plugin-orders/src/util/addShipmentMethodToGroup.js b/packages/api-plugin-orders/src/util/addShipmentMethodToGroup.js index 3fb25620b7b..1aaf2a9f0ae 100644 --- a/packages/api-plugin-orders/src/util/addShipmentMethodToGroup.js +++ b/packages/api-plugin-orders/src/util/addShipmentMethodToGroup.js @@ -42,16 +42,18 @@ export default async function addShipmentMethodToGroup(context, { const rates = await queries.getFulfillmentMethodsWithQuotes(commonOrder, context); const errorResult = rates.find((option) => option.requestStatus === "error"); if (errorResult) { - throw new ReactionError("invalid", errorResult.message); + const eventData = { field: "FulfillmentMethod", value: "Returned error" }; + throw new ReactionError("invalid", errorResult.message, eventData); } const selectedFulfillmentMethod = rates.find((rate) => selectedFulfillmentMethodId === rate.method._id); if (!selectedFulfillmentMethod) { + const eventData = { field: "selectedFulfillmentMethodId", value: selectedFulfillmentMethodId }; throw new ReactionError("invalid", "The selected fulfillment method is no longer available." + - " Fetch updated fulfillment options and try creating the order again with a valid method."); + " Fetch updated fulfillment options and try creating the order again with a valid method.", eventData); } - group.shipmentMethod = { + const output = { _id: selectedFulfillmentMethod.method._id, carrier: selectedFulfillmentMethod.method.carrier, currencyCode, @@ -61,4 +63,9 @@ export default async function addShipmentMethodToGroup(context, { handling: selectedFulfillmentMethod.handlingPrice, rate: selectedFulfillmentMethod.rate }; + // Include methodAdditionalData only if available + if (selectedFulfillmentMethod?.method?.methodAdditionalData) { + output.methodAdditionalData = selectedFulfillmentMethod.method.methodAdditionalData; + } + return output; } diff --git a/packages/api-plugin-orders/src/util/orderValidators/createPayments.js b/packages/api-plugin-orders/src/util/orderValidators/createPayments.js new file mode 100644 index 00000000000..6bb7723326f --- /dev/null +++ b/packages/api-plugin-orders/src/util/orderValidators/createPayments.js @@ -0,0 +1,110 @@ +import ReactionError from "@reactioncommerce/reaction-error"; +import Logger from "@reactioncommerce/logger"; +import verifyPaymentsMatchOrderTotal from "../verifyPaymentsMatchOrderTotal.js"; +import { Payment as PaymentSchema } from "../../simpleSchemas.js"; + +/** + * @summary Create all authorized payments for a potential order + * @param {String} [accountId] The ID of the account placing the order + * @param {Object} [billingAddress] Billing address for the order as a whole + * @param {Object} context - The application context + * @param {String} currencyCode Currency code for interpreting the amount of all payments + * @param {String} email Email address for the order + * @param {Number} orderTotal Total due for the order + * @param {Object[]} paymentsInput List of payment inputs + * @param {Object} [shippingAddress] Shipping address, if relevant, for fraud detection + * @param {String} shop shop that owns the order + * @returns {Object[]} Array of created payments + */ +export default async function createPayments({ + accountId, + billingAddress, + context, + currencyCode, + email, + orderTotal, + paymentsInput, + shippingAddress, + shop, + flag +}) { + // Determining which payment methods are enabled for the shop + const availablePaymentMethods = shop.availablePaymentMethods || []; + + // Verify that total of payment inputs equals total due. We need to be sure + // to do this before creating any payment authorizations + verifyPaymentsMatchOrderTotal(paymentsInput || [], orderTotal); + + // Create authorized payments for each + const paymentPromises = (paymentsInput || []).map(async (paymentInput) => { + const { amount, method: methodName } = paymentInput; + + // Verify that this payment method is enabled for the shop + if (!availablePaymentMethods.includes(methodName)) { + const errorName = "payment-failed"; + const errorMessage = `Payment method not enabled for this shop: ${methodName}`; + const eventData = { field: "Payment Method Name", value: methodName }; + throw new ReactionError(errorName, errorMessage, eventData); + } + + // Grab config for this payment method + let paymentMethodConfig; + try { + paymentMethodConfig = context.queries.getPaymentMethodConfigByName(methodName); + } catch (error) { + Logger.error(error); + const errorName = "payment-failed"; + const errorMessage = `Invalid payment method name: ${methodName}`; + const eventData = { field: "Payment Method Name", value: methodName }; + throw new ReactionError(errorName, errorMessage, eventData); + } + + // Authorize this payment - skip if validateOrder + let payment = {}; + if (flag === "createOrderObject") { + try { + payment = await paymentMethodConfig.functions.createAuthorizedPayment(context, { + accountId, // optional + amount, + billingAddress: paymentInput.billingAddress || billingAddress, + currencyCode, + email, + shippingAddress, // optional, for fraud detection, the first shipping address if shipping to multiple + shopId: shop._id, + paymentData: { + ...(paymentInput.data || {}) + } // optional, object, blackbox + }); + } catch (err) { + throw new ReactionError("payment-failed", "Error in authorizing payment"); + } + } + + const paymentWithCurrency = { + ...payment, + // This is from previous support for exchange rates, which was removed in v3.0.0 + currency: { exchangeRate: 1, userCurrency: currencyCode }, + currencyCode + }; + + // If flag === validateOrder, we are not authorizing payment and we do not have payment object to validate + if (flag === "createOrderObject") { + PaymentSchema.validate(paymentWithCurrency); + } + + return paymentWithCurrency; + }); + + let payments = {}; + if (flag === "createOrderObject") { + try { + payments = await Promise.all(paymentPromises); + payments = payments.filter((payment) => !!payment); // remove nulls + } catch (error) { + Logger.error("createOrder: error creating payments", error.message); + throw new ReactionError("payment-failed", `There was a problem authorizing this payment: ${error.message}`); + } + } + + return payments; +} diff --git a/packages/api-plugin-orders/src/util/orderValidators/getCustomFields.js b/packages/api-plugin-orders/src/util/orderValidators/getCustomFields.js new file mode 100644 index 00000000000..6f50ff0f9d7 --- /dev/null +++ b/packages/api-plugin-orders/src/util/orderValidators/getCustomFields.js @@ -0,0 +1,25 @@ +/** + * @method getCustomFields + * @summary Returns the CustomFields + * @param {Object} context - an object containing the per-request state + * @param {Object} customFieldsFromClient - customFieldsFromClient + * @param {Object} order - orderobject + * @returns {Object[]} customFields + */ +export default async function getCustomFields(context, customFieldsFromClient, order) { + const { getFunctionsOfType } = context; + + // Apply custom order data transformations from plugins + const transformCustomOrderFieldsFuncs = getFunctionsOfType("transformCustomOrderFields"); + let customFields = { ...(customFieldsFromClient || {}) }; + if (transformCustomOrderFieldsFuncs.length > 0) { + // We need to run each of these functions in a series, rather than in parallel, because + // each function expects to get the result of the previous. It is recommended to disable `no-await-in-loop` + // eslint rules when the output of one iteration might be used as input in another iteration, such as this case here. + // See https://eslint.org/docs/rules/no-await-in-loop#when-not-to-use-it + for (const transformCustomOrderFieldsFunc of transformCustomOrderFieldsFuncs) { + customFields = await transformCustomOrderFieldsFunc({ context, customFields, order }); // eslint-disable-line no-await-in-loop + } + } + return customFields; +} diff --git a/packages/api-plugin-orders/src/util/orderValidators/getDiscounts.js b/packages/api-plugin-orders/src/util/orderValidators/getDiscounts.js new file mode 100644 index 00000000000..8e3cccd2e1f --- /dev/null +++ b/packages/api-plugin-orders/src/util/orderValidators/getDiscounts.js @@ -0,0 +1,17 @@ +/** + * @method getCustomFields + * @summary Returns the CustomFields + * @param {Object} context - an object containing the per-request state + * @param {Object} cart - cart object + * @returns {Object[]} customFields + */ +export default async function getDiscounts(context, cart) { + let discounts = []; + let discountTotal = 0; + if (cart) { + const discountsResult = await context.queries.getDiscountsTotalForCart(context, cart); + ({ discounts } = discountsResult); + discountTotal = discountsResult.total; + } + return { discounts, discountTotal }; +} diff --git a/packages/api-plugin-orders/src/util/orderValidators/getFinalFulfillmentGroups.js b/packages/api-plugin-orders/src/util/orderValidators/getFinalFulfillmentGroups.js new file mode 100644 index 00000000000..9dcc95d6b33 --- /dev/null +++ b/packages/api-plugin-orders/src/util/orderValidators/getFinalFulfillmentGroups.js @@ -0,0 +1,147 @@ +import Random from "@reactioncommerce/random"; +import buildOrderItem from "../buildOrderItem.js"; +import addInvoiceToGroup from "../addInvoiceToGroup.js"; +import addShipmentMethodToGroup from "../addShipmentMethodToGroup.js"; +import addTaxesToGroup from "../addTaxesToGroup.js"; +import compareExpectedAndActualTotals from "../compareExpectedAndActualTotals.js"; +import getSurchargesForGroup from "../getSurchargesForGroup.js"; + +/** + * @summary Create all authorized payments for a potential order + * @param {Object} context - The application context + * @param {Object} inputData - The input data + * @param {String} orderId ID of existing or new order to which this group will belong + * @param {String} [accountId] ID of account that is placing or already did place the order + * @param {Object} [billingAddress] The primary billing address for the order, if known + * @param {String} [cartId] ID of the cart from which the order is being placed, if applicable + * @param {String} currencyCode Currency code for all money values + * @param {Number} [discountTotal] Calculated discount total + * @param {Object} [fulfillmentGroups] Fulfillment group input + * @param {Object} cart Cart object + * @returns {Promise} Object with surcharge, total, shippingAddress and finalFulfillment groups + */ +export default async function getFinalFulfillmentGroups(context, inputData) { + const { + orderId, + accountId, + additionalItems, + billingAddress, + cartId, + currencyCode, + discountTotal, + fulfillmentGroups, + cart + } = inputData; + + // Create array for surcharges to apply to order, if applicable + // Array is populated inside `fulfillmentGroups.map()` + const orderSurcharges = []; + let shippingAddressForPayments = null; + let orderTotal = 0; + + // Add more props to each fulfillment group, and validate/build the items in each group + + const finalFulfillmentGroups = await Promise.all(fulfillmentGroups.map(async (inputGroup) => { + const { data, items, selectedFulfillmentMethodId, shopId, totalPrice: expectedGroupTotal, type } = inputGroup; + const group = { + _id: Random.id(), + address: data ? data.shippingAddress : null, + shopId, + type, + workflow: { status: "new", workflow: ["new"] } + }; + + // Build the final order item objects. As part of this, we look up the variant in the system and make sure that + // the price is what the caller expects it to be. + if (items) { + group.items = await Promise.all(items.map((inputItem) => buildOrderItem(context, { currencyCode, inputItem, cart }))); + } else { + group.items = []; + } + + if (Array.isArray(additionalItems) && additionalItems.length) { + group.items.push(...additionalItems); + } + + // Add some more properties for convenience + group.itemIds = group.items.map((item) => item._id); + group.totalItemQuantity = group.items.reduce((sum, item) => sum + item.quantity, 0); + + // Apply shipment method + group.shipmentMethod = await addShipmentMethodToGroup(context, { + accountId, + billingAddress, + cartId, + currencyCode, + discountTotal, + group, + orderId, + selectedFulfillmentMethodId + }); + + const { + groupSurcharges, + groupSurchargeTotal + } = await getSurchargesForGroup(context, { + accountId, + billingAddress, + cartId, + currencyCode, + discountTotal, + group, + orderId, + selectedFulfillmentMethodId + }); + + // Calculate and set taxes. Mutates group object in addition to returning the totals. + const { taxTotal, taxableAmount } = await addTaxesToGroup(context, { + accountId, + billingAddress, + cartId, + currencyCode, + discountTotal, + group, + orderId, + surcharges: groupSurcharges + }); + + // Build and set the group invoice + group.invoice = addInvoiceToGroup({ + currencyCode, + group, + groupDiscountTotal: discountTotal, + groupSurchargeTotal, + taxableAmount, + taxTotal + }); + + if (expectedGroupTotal) { + // For now we expect that the client has NOT included discounts in the expected total it sent. + // Note that we don't currently know which parts of `discountTotal` go with which fulfillment groups. + // This needs to be rewritten soon for discounts to work when there are multiple fulfillment groups. + // Probably the client should be sending all applied discount IDs and amounts in the order input (by group), + // and include total discount in `groupInput.totalPrice`, and then we simply verify that they are valid here. + const expectedTotal = Math.max(expectedGroupTotal - discountTotal, 0); + + // Compare expected and actual totals to make sure client sees correct calculated price + // Error if we calculate total price differently from what the client has shown as the preview. + // It's important to keep this after adding and verifying the shipmentMethod and order item prices. + compareExpectedAndActualTotals(group.invoice.total, expectedTotal); + } + + // We save off the first shipping address found, for passing to payment services. They use this + // for fraud detection. + if (group.address && !shippingAddressForPayments) shippingAddressForPayments = group.address; + + // Push all group surcharges to overall order surcharge array. + // Currently, we do not save surcharges per group + orderSurcharges.push(...groupSurcharges); + + // Add the group total to the order total + orderTotal += group.invoice.total; + + return group; + })); + + return { orderSurcharges, orderTotal, shippingAddressForPayments, finalFulfillmentGroups }; +} diff --git a/packages/api-plugin-orders/src/util/orderValidators/getReferenceId.js b/packages/api-plugin-orders/src/util/orderValidators/getReferenceId.js new file mode 100644 index 00000000000..53da69d6826 --- /dev/null +++ b/packages/api-plugin-orders/src/util/orderValidators/getReferenceId.js @@ -0,0 +1,39 @@ +import _ from "lodash"; +import Random from "@reactioncommerce/random"; +import ReactionError from "@reactioncommerce/reaction-error"; +import Logger from "@reactioncommerce/logger"; + +/** + * @method getReferenceId + * @summary Returns the referenceId + * @param {Object} context - an object containing the per-request state + * @param {Object} cart - Cart object + * @param {Object} order - Order object + * @returns {String} referenceId + */ +export default async function getReferenceId(context, cart, order) { + const { getFunctionsOfType } = context; + let referenceId; + const createReferenceIdFunctions = getFunctionsOfType("createOrderReferenceId"); + + if (!createReferenceIdFunctions || createReferenceIdFunctions.length === 0) { + // if the cart has a reference Id, and no custom function is created use that + if (_.get(cart, "referenceId")) { // we want the else to fallthrough if no cart to keep the if/else logic simple + ({ referenceId } = cart); + } else { + referenceId = Random.id(); + } + } else { + referenceId = await createReferenceIdFunctions[0](context, order, cart); + if (typeof referenceId !== "string") { + const errorName = "invalid-parameter"; + const errorMessage = "Non-string value for Reference Id"; + throw new ReactionError(errorName, errorMessage); + } + if (createReferenceIdFunctions.length > 1) { + Logger.warn("More than one createOrderReferenceId function defined. Using first one defined"); + } + } + + return referenceId; +} diff --git a/packages/api-plugin-orders/src/util/orderValidators/prepareOrder.js b/packages/api-plugin-orders/src/util/orderValidators/prepareOrder.js new file mode 100644 index 00000000000..d378d8756b7 --- /dev/null +++ b/packages/api-plugin-orders/src/util/orderValidators/prepareOrder.js @@ -0,0 +1,314 @@ +import SimpleSchema from "simpl-schema"; +import Random from "@reactioncommerce/random"; +import getAnonymousAccessToken from "@reactioncommerce/api-utils/getAnonymousAccessToken.js"; +import { Order as OrderSchema, orderInputSchema, paymentInputSchema } from "../../simpleSchemas.js"; +import validateInitialOrderData from "./validateInitialOrderData.js"; +import getDiscounts from "./getDiscounts.js"; +import getFinalFulfillmentGroups from "./getFinalFulfillmentGroups.js"; +import createPayments from "./createPayments.js"; +import getReferenceId from "./getReferenceId.js"; +import getCustomFields from "./getCustomFields.js"; + +const inputSchema = new SimpleSchema({ + "order": orderInputSchema, + "payments": { + type: Array, + optional: true + }, + "payments.$": paymentInputSchema +}); + +/** + * @summary Formats validation error to common error format + * @param {Object} err Validation error object + * @returns {Object[]} Array of error entries + */ +function formatErrors(err) { + let errorEntries = []; + if (err.errorType === "ClientError" && err.error === "validation-error") { + errorEntries = (err.details || []).map((errorEntry) => { + const errorObject = { + errorName: "validation-error", + errorType: "Schema validation Error", + errorField: errorEntry.name || null, + fieldValue: errorEntry.value || null, + errorMessage: errorEntry.message || "No details" + }; + return errorObject; + }); + } else { + let errorField; + let fieldValue; + if (err.eventData) { + errorField = err.eventData.field; + fieldValue = err.eventData.value; + } + const errorObject = { + errorName: err.error, + errorType: "ReactionError", + errorField: errorField || null, + fieldValue: fieldValue || null, + errorMessage: err.reason || "No details" + }; + errorEntries.push(errorObject); + } + return errorEntries; +} + +/** + * @name prepareOrder + * @method + * @memberof Order/NoMeteorQueries + * @summary Validates if the input order details is valid and ready for order processing + * @param {Object} context - an object containing the per-request state + * @param {Object} input - order details, refer inputSchema + * @param {String} flag - flag which define if the call is from placeOrder or validateOrder + * @returns {Promise} output - order, token and validation results + */ +export default async function prepareOrder(context, input, flag) { + const cleanedInput = inputSchema.clean(input); + const validationResults = []; + + // Step01: Input data validation against input schema + if (flag === "createOrderObject") { + inputSchema.validate(cleanedInput); + } else { + try { + inputSchema.validate(cleanedInput); + } catch (err) { + const validationErrors = formatErrors(err); + validationResults.push(...validationErrors); + return { errors: validationResults, success: false }; + } + } + + // Step02: Initial validation for shop/cart/user-id + let initialValidationResult; + if (flag === "createOrderObject") { + initialValidationResult = await validateInitialOrderData(context, cleanedInput); + } else { + try { + initialValidationResult = await validateInitialOrderData(context, cleanedInput); + } catch (err) { + const validationErrors = formatErrors(err); + validationResults.push(...validationErrors); + return { errors: validationResults, success: false }; + } + } + const { shop, cart } = initialValidationResult; + + // Step03: Extract the rest of the required variables + const { order: orderInput, payments: paymentsInput } = cleanedInput; + const { + billingAddress, + cartId, + currencyCode, + customFields: customFieldsFromClient, + email, + fulfillmentGroups, + ordererPreferredLanguage, + shopId + } = orderInput; + const { accountId } = context; + + + // Step04: Getting discount details. If no data, we get back empty values + let getDiscountsResult; + try { + getDiscountsResult = await getDiscounts(context, cart); + } catch (err) { + const validationErrors = formatErrors(err); + validationResults.push(...validationErrors); + } + const { discounts, discountTotal } = getDiscountsResult; + + // Create array for surcharges to apply to order, if applicable + // Array is populated inside `fulfillmentGroups.map()` + let orderSurcharges = []; + + // Create orderId + const orderId = Random.id(); + + + // Add more props to each fulfillment group, and validate/build the items in each group + let orderTotal = 0; + let shippingAddressForPayments = null; + + // Step + let finalFulfillmentGroups = []; + + if (flag === "createOrderObject") { + ({ + orderSurcharges, + orderTotal, + shippingAddressForPayments, + finalFulfillmentGroups + } = await getFinalFulfillmentGroups(context, { + orderId, + accountId, + billingAddress, + cartId, + currencyCode, + discountTotal, + fulfillmentGroups, + cart + })); + } else { + try { + ({ + orderSurcharges, + orderTotal, + shippingAddressForPayments, + finalFulfillmentGroups + } = await getFinalFulfillmentGroups(context, { + orderId, + accountId, + billingAddress, + cartId, + currencyCode, + discountTotal, + fulfillmentGroups, + cart + })); + } catch (err) { + if (!err.eventData) { + err.eventData = { + field: "Fulfillment Group", + value: "Invalid" + }; + } + const validationErrors = formatErrors(err); + validationResults.push(...validationErrors); + } + } + + const allValidateFuncs = context.getFunctionsOfType("validateOrderMethods"); + // Collect results from validation specific to each fulfillment-method + for (const group of finalFulfillmentGroups) { + const requiredMethodFunctionName = `validateOrderMethods${group.shipmentMethod.name}`; + const requiredMethodFunction = allValidateFuncs.find((fn) => fn.name === requiredMethodFunctionName); + + if (requiredMethodFunction) { + // eslint-disable-next-line no-await-in-loop + await requiredMethodFunction(context, orderInput, validationResults); + } + } + + let payments; + if (flag === "createOrderObject") { + payments = await createPayments({ + accountId, + billingAddress, + context, + currencyCode, + email, + orderTotal, + paymentsInput, + shippingAddress: shippingAddressForPayments, + shop, + flag // Pass on the same flag we received for prepareOrder + }); + } else { + try { + payments = await createPayments({ + accountId, + billingAddress, + context, + currencyCode, + email, + orderTotal, + paymentsInput, + shippingAddress: shippingAddressForPayments, + shop, + flag // Pass on the same flag we received for prepareOrder + }); + } catch (err) { + const validationErrors = formatErrors(err); + validationResults.push(...validationErrors); + } + } + + // Create anonymousAccessToken if no account ID + const fullToken = accountId ? null : getAnonymousAccessToken(); + + const now = new Date(); + + const order = { + _id: orderId, + accountId, + billingAddress, + cartId, + createdAt: now, + currencyCode, + discounts, + email, + ordererPreferredLanguage: ordererPreferredLanguage || null, + shipping: finalFulfillmentGroups, + shopId, + surcharges: orderSurcharges, + totalItemQuantity: finalFulfillmentGroups.reduce((sum, group) => sum + group.totalItemQuantity, 0), + updatedAt: now, + workflow: { + status: "new", + workflow: ["new"] + } + }; + + if (flag === "createOrderObject") { + order.payments = payments; + } + + if (fullToken) { + const dbToken = { ...fullToken }; + // don't store the raw token in db, only the hash + delete dbToken.token; + order.anonymousAccessTokens = [dbToken]; + } + + + let referenceId; + if (flag === "createOrderObject") { + referenceId = await getReferenceId(context, cart, order); + } else { + try { + referenceId = await getReferenceId(context, cart, order); + } catch (err) { + const validationErrors = formatErrors(err); + validationResults.push(...validationErrors); + } + } + order.referenceId = referenceId; + + let customFields; + if (flag === "createOrderObject") { + customFields = await getCustomFields(context, customFieldsFromClient, order); + } else { + try { + customFields = await getCustomFields(context, customFieldsFromClient, order); + } catch (err) { + const validationErrors = formatErrors(err); + validationResults.push(...validationErrors); + } + } + order.customFields = customFields; + + // Validate and Return + let success = !(validationResults && validationResults.length > 0); + + let output; + if (flag === "createOrderObject") { + OrderSchema.validate(order); + output = { order, fullToken, errors: validationResults, success }; + } else { // flag expected to be "validateOrder" + const OrderWithoutPaymentsSchema = OrderSchema.omit("payments"); + try { + OrderWithoutPaymentsSchema.validate(order); + } catch (err) { + const validationErrors = formatErrors(err); + validationResults.push(...validationErrors); + success = false; + } + output = { order: null, fullToken: null, errors: validationResults, success }; + } + return output; +} diff --git a/packages/api-plugin-orders/src/util/orderValidators/validateInitialOrderData.js b/packages/api-plugin-orders/src/util/orderValidators/validateInitialOrderData.js new file mode 100644 index 00000000000..8d673920318 --- /dev/null +++ b/packages/api-plugin-orders/src/util/orderValidators/validateInitialOrderData.js @@ -0,0 +1,34 @@ +import ReactionError from "@reactioncommerce/reaction-error"; + +/** + * @method validateInitialOrderData + * @summary Performs the initial validation on the OrderData + * @param {Object} context - an object containing the per-request state + * @param {Object} cleanedInput - Necessary input. See SimpleSchema + * @returns {Promise} Object with `order` property containing the created order + */ +export default async function validateInitialOrderData(context, cleanedInput) { + const { order: orderInput } = cleanedInput; + const { cartId, shopId } = orderInput; + const { collections, userId } = context; + const { Cart } = collections; + + if (!shopId) throw new ReactionError("invalid-param", "ShopID not found in order data", { field: "ShopId", value: shopId }); + + const shop = await context.queries.shopById(context, shopId); + if (!shop) throw new ReactionError("not-found", "Shop not found while trying to validate order data", { field: "ShopId", value: shopId }); + + let cart; + if (cartId) { + cart = await Cart.findOne({ _id: cartId }); + if (!cart) { + throw new ReactionError("not-found", "Cart not found while trying to validate order data", { field: "CartId", value: cartId }); + } + } + + if (!userId && !shop.allowGuestCheckout) { + throw new ReactionError("access-denied", "Guest checkout not allowed"); + } + + return { shop, cart }; +} diff --git a/packages/api-plugin-orders/src/util/updateGroupTotals.js b/packages/api-plugin-orders/src/util/updateGroupTotals.js index 48c4ba98d58..e1c28b808fa 100644 --- a/packages/api-plugin-orders/src/util/updateGroupTotals.js +++ b/packages/api-plugin-orders/src/util/updateGroupTotals.js @@ -32,7 +32,7 @@ export default async function updateGroupTotals(context, { selectedFulfillmentMethodId }) { // Apply shipment method - await addShipmentMethodToGroup(context, { + group.shipmentMethod = await addShipmentMethodToGroup(context, { accountId, billingAddress, cartId, @@ -70,7 +70,7 @@ export default async function updateGroupTotals(context, { }); // Build and set the group invoice - addInvoiceToGroup({ + group.invoice = addInvoiceToGroup({ currencyCode, group, groupDiscountTotal: discountTotal, From 0520273de7c31d3c20436c721a4c65c16f6e4aa4 Mon Sep 17 00:00:00 2001 From: Sujith Date: Tue, 25 Oct 2022 14:27:02 +0530 Subject: [PATCH 35/71] feat: first set of tests placeOrder refactor Signed-off-by: Sujith --- .../orderValidators/getCustomFields.test.js | 23 ++++++++++ .../src/util/orderValidators/getDiscounts.js | 12 +++--- .../util/orderValidators/getDiscounts.test.js | 41 ++++++++++++++++++ .../getFinalFulfillmentGroups.js | 3 +- .../orderValidators/getReferenceId.test.js | 42 ++++++++++++++++++ .../validateInitialOrderData.test.js | 43 +++++++++++++++++++ 6 files changed, 158 insertions(+), 6 deletions(-) create mode 100644 packages/api-plugin-orders/src/util/orderValidators/getCustomFields.test.js create mode 100644 packages/api-plugin-orders/src/util/orderValidators/getDiscounts.test.js create mode 100644 packages/api-plugin-orders/src/util/orderValidators/getReferenceId.test.js create mode 100644 packages/api-plugin-orders/src/util/orderValidators/validateInitialOrderData.test.js diff --git a/packages/api-plugin-orders/src/util/orderValidators/getCustomFields.test.js b/packages/api-plugin-orders/src/util/orderValidators/getCustomFields.test.js new file mode 100644 index 00000000000..9bb44073370 --- /dev/null +++ b/packages/api-plugin-orders/src/util/orderValidators/getCustomFields.test.js @@ -0,0 +1,23 @@ +/* eslint-disable require-jsdoc */ +import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; +import getCustomFields from "./getCustomFields.js"; + +test("should return original fields if there are NO functions defined", async () => { + mockContext.getFunctionsOfType = jest.fn().mockReturnValueOnce([]); + + const orderInput = { orderId: "order123" }; + const customFieldsFromClient = { customField1: "customValue1" }; + const result = await getCustomFields(mockContext, customFieldsFromClient, orderInput); + expect(result).toEqual({ customField1: "customValue1" }); +}); + +test("should return transformed fields if there are functions defined", async () => { + mockContext.getFunctionsOfType = jest.fn().mockReturnValueOnce([ + jest.fn().mockName("transformCustomOrderFields").mockReturnValueOnce(Promise.resolve({ customField2: "customValue2" })) + ]); + + const orderInput = { orderId: "order123" }; + const customFieldsFromClient = { customField1: "customValue1" }; + const result = await getCustomFields(mockContext, customFieldsFromClient, orderInput); + expect(result).toEqual({ customField2: "customValue2" }); +}); diff --git a/packages/api-plugin-orders/src/util/orderValidators/getDiscounts.js b/packages/api-plugin-orders/src/util/orderValidators/getDiscounts.js index 8e3cccd2e1f..dc761a31e41 100644 --- a/packages/api-plugin-orders/src/util/orderValidators/getDiscounts.js +++ b/packages/api-plugin-orders/src/util/orderValidators/getDiscounts.js @@ -1,17 +1,19 @@ /** - * @method getCustomFields - * @summary Returns the CustomFields + * @method getDiscounts + * @summary Returns the Discount Total for cart * @param {Object} context - an object containing the per-request state * @param {Object} cart - cart object - * @returns {Object[]} customFields + * @returns {Object} discounts and discountTotal */ export default async function getDiscounts(context, cart) { let discounts = []; let discountTotal = 0; if (cart) { const discountsResult = await context.queries.getDiscountsTotalForCart(context, cart); - ({ discounts } = discountsResult); - discountTotal = discountsResult.total; + if (discountsResult) { + ({ discounts } = discountsResult); + discountTotal = discountsResult.total; + } } return { discounts, discountTotal }; } diff --git a/packages/api-plugin-orders/src/util/orderValidators/getDiscounts.test.js b/packages/api-plugin-orders/src/util/orderValidators/getDiscounts.test.js new file mode 100644 index 00000000000..d2f8b076900 --- /dev/null +++ b/packages/api-plugin-orders/src/util/orderValidators/getDiscounts.test.js @@ -0,0 +1,41 @@ +/* eslint-disable require-jsdoc */ +import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; +import getDiscounts from "./getDiscounts.js"; + +test("should return empmty fields if no cart details are passed", async () => { + const result = await getDiscounts(mockContext); + expect(result).toEqual({ + discounts: [], + discountTotal: 0 + }); +}); + +test("should return empmty fields if no discount functions defined", async () => { + mockContext.queries.getDiscountsTotalForCart = jest.fn().mockReturnValueOnce(undefined); + + const cartInput = { cartId: "cart123" }; + const result = await getDiscounts(mockContext, cartInput); + expect(result).toEqual({ + discounts: [], + discountTotal: 0 + }); +}); + +test("should return discount details if there are discount functions defined", async () => { + mockContext.queries.getDiscountsTotalForCart = jest.fn().mockName("getDiscountsTotalForCart").mockReturnValueOnce({ + discounts: [{ + discountId: "discountId", amount: 10 + }], + total: 10 + }); + + const cartInput = { cartId: "cart123" }; + const result = await getDiscounts(mockContext, cartInput); + + expect(result).toEqual({ + discounts: [{ + discountId: "discountId", amount: 10 + }], + discountTotal: 10 + }); +}); diff --git a/packages/api-plugin-orders/src/util/orderValidators/getFinalFulfillmentGroups.js b/packages/api-plugin-orders/src/util/orderValidators/getFinalFulfillmentGroups.js index 9dcc95d6b33..e1743451848 100644 --- a/packages/api-plugin-orders/src/util/orderValidators/getFinalFulfillmentGroups.js +++ b/packages/api-plugin-orders/src/util/orderValidators/getFinalFulfillmentGroups.js @@ -7,11 +7,12 @@ import compareExpectedAndActualTotals from "../compareExpectedAndActualTotals.js import getSurchargesForGroup from "../getSurchargesForGroup.js"; /** - * @summary Create all authorized payments for a potential order + * @summary Create fulfillment groups for a potential order * @param {Object} context - The application context * @param {Object} inputData - The input data * @param {String} orderId ID of existing or new order to which this group will belong * @param {String} [accountId] ID of account that is placing or already did place the order + * @param {Object} [additionalItems] Additional items if any * @param {Object} [billingAddress] The primary billing address for the order, if known * @param {String} [cartId] ID of the cart from which the order is being placed, if applicable * @param {String} currencyCode Currency code for all money values diff --git a/packages/api-plugin-orders/src/util/orderValidators/getReferenceId.test.js b/packages/api-plugin-orders/src/util/orderValidators/getReferenceId.test.js new file mode 100644 index 00000000000..697afd0e346 --- /dev/null +++ b/packages/api-plugin-orders/src/util/orderValidators/getReferenceId.test.js @@ -0,0 +1,42 @@ +/* eslint-disable require-jsdoc */ +import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; +import ReactionError from "@reactioncommerce/reaction-error"; +import getReferenceId from "./getReferenceId.js"; + +test("should return referenceId from Cart or Random if there are NO functions defined", async () => { + mockContext.getFunctionsOfType = jest.fn().mockReturnValueOnce([]); + + const orderInput = { orderId: "order123" }; + + const cartInput1 = { cartId: "cart123", referenceId: "referenceId123" }; + const result1 = await getReferenceId(mockContext, cartInput1, orderInput); + expect(result1).toEqual("referenceId123"); + + const cartInput2 = { cartId: "cart123" }; + const result2 = await getReferenceId(mockContext, cartInput2, orderInput); + expect(result2).toEqual(jasmine.any(String)); +}); + +test("should return referenceId from function if there are functions defined", async () => { + mockContext.getFunctionsOfType = jest.fn().mockReturnValueOnce([ + jest.fn().mockName("createOrderReferenceId").mockReturnValueOnce(Promise.resolve("referenceId321")) + ]); + + const orderInput = { orderId: "order123" }; + const cartInput = { cartId: "cart123", referenceId: "referenceId123" }; + + const result = await getReferenceId(mockContext, cartInput, orderInput); + expect(result).toEqual("referenceId321"); +}); + +test("should throw error if returned referenceId from function is not string", async () => { + mockContext.getFunctionsOfType = jest.fn().mockReturnValueOnce([ + jest.fn().mockName("createOrderReferenceId").mockReturnValueOnce(Promise.resolve(1234)) + ]); + + const orderInput = { orderId: "order123" }; + const cartInput = { cartId: "cart123", referenceId: "referenceId123" }; + + const errorExpected = new ReactionError("invalid-parameter", "Non-string value for Reference Id"); + await expect(getReferenceId(mockContext, cartInput, orderInput)).rejects.toThrow(errorExpected); +}); diff --git a/packages/api-plugin-orders/src/util/orderValidators/validateInitialOrderData.test.js b/packages/api-plugin-orders/src/util/orderValidators/validateInitialOrderData.test.js new file mode 100644 index 00000000000..1308cd7a6a3 --- /dev/null +++ b/packages/api-plugin-orders/src/util/orderValidators/validateInitialOrderData.test.js @@ -0,0 +1,43 @@ +/* eslint-disable require-jsdoc */ +import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; +import ReactionError from "@reactioncommerce/reaction-error"; +import validateInitialOrderData from "./validateInitialOrderData.js"; + +test("should throw if no shopId provided", async () => { + const cleanedInput = { order: { cartId: "cart123" } }; + const errorExpected = new ReactionError("invalid-param", "ShopID not found in order data", { field: "ShopId", value: undefined }); + await expect(validateInitialOrderData(mockContext, cleanedInput)).rejects.toThrow(errorExpected); +}); + +test("should throw if no shop retrieved using shopId provided", async () => { + mockContext.queries.shopById = jest.fn().mockReturnValueOnce(undefined); + const cleanedInput = { order: { cartId: "cart123", shopId: "shop123" } }; + const errorExpected = new ReactionError("not-found", "Shop not found while trying to validate order data", { field: "ShopId", value: "shop123" }); + await expect(validateInitialOrderData(mockContext, cleanedInput)).rejects.toThrow(errorExpected); +}); + +test("should throw if no cart retrieved using cartId provided", async () => { + mockContext.queries.shopById = jest.fn().mockReturnValueOnce({ shopId: "shop123" }); + mockContext.collections.Cart.findOne = jest.fn().mockReturnValueOnce(undefined); + const cleanedInput = { order: { cartId: "cart123", shopId: "shop123" } }; + const errorExpected = new ReactionError("not-found", "Cart not found while trying to validate order data", { field: "CartId", value: "cart123" }); + await expect(validateInitialOrderData(mockContext, cleanedInput)).rejects.toThrow(errorExpected); +}); + +test("should return shop and cart details", async () => { + mockContext.queries.shopById = jest.fn().mockReturnValueOnce({ shopId: "shop123" }); + mockContext.collections.Cart.findOne = jest.fn().mockReturnValueOnce({ cartId: "cart123" }); + const cleanedInput = { order: { cartId: "cart123", shopId: "shop123" } }; + const resultExpected = { cart: { cartId: "cart123" }, shop: { shopId: "shop123" } }; + const result = await validateInitialOrderData(mockContext, cleanedInput); + expect(result).toEqual(resultExpected); +}); + +test("should throw if no userId and No guest checkout", async () => { + mockContext.userId = undefined; + mockContext.queries.shopById = jest.fn().mockReturnValueOnce({ shopId: "shop123" }); + mockContext.collections.Cart.findOne = jest.fn().mockReturnValueOnce({ cartId: "cart123" }); + const cleanedInput = { order: { cartId: "cart123", shopId: "shop123" } }; + const errorExpected = new ReactionError("access-denied", "Guest checkout not allowed"); + await expect(validateInitialOrderData(mockContext, cleanedInput)).rejects.toThrow(errorExpected); +}); From 07c5436023d0f911cb9527b051a97ebd90fe0a3c Mon Sep 17 00:00:00 2001 From: Sujith Date: Fri, 28 Oct 2022 01:24:01 +0530 Subject: [PATCH 36/71] fix: review comments fixes Signed-off-by: Sujith --- .../api-plugin-orders/src/mutations/placeOrder.js | 3 +-- .../api-plugin-orders/src/queries/validateOrder.js | 2 +- .../src/util/orderValidators/prepareOrder.js | 14 ++++++++------ .../orderValidators/validateInitialOrderData.js | 3 +-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/api-plugin-orders/src/mutations/placeOrder.js b/packages/api-plugin-orders/src/mutations/placeOrder.js index de858d959e8..7a49a87e403 100644 --- a/packages/api-plugin-orders/src/mutations/placeOrder.js +++ b/packages/api-plugin-orders/src/mutations/placeOrder.js @@ -8,8 +8,7 @@ import prepareOrder from "../util/orderValidators/prepareOrder.js"; * @returns {Promise} Object with `order` property containing the created order */ export default async function placeOrder(context, input) { - const { appEvents, collections, userId } = context; - const { Orders } = collections; + const { appEvents, collections: { Orders }, userId } = context; const { order, fullToken } = await prepareOrder(context, input, "createOrderObject"); await Orders.insertOne(order); diff --git a/packages/api-plugin-orders/src/queries/validateOrder.js b/packages/api-plugin-orders/src/queries/validateOrder.js index 88478a69267..1703ad04903 100644 --- a/packages/api-plugin-orders/src/queries/validateOrder.js +++ b/packages/api-plugin-orders/src/queries/validateOrder.js @@ -3,7 +3,7 @@ import prepareOrder from "../util/orderValidators/prepareOrder.js"; /** * @name validateOrder * @method - * @memberof Order/NoMeteorQueries + * @memberof Order * @summary Validates if the input order details is valid and ready for order processing * @param {Object} context - an object containing the per-request state * @param {Object} input - order details, refer inputSchema diff --git a/packages/api-plugin-orders/src/util/orderValidators/prepareOrder.js b/packages/api-plugin-orders/src/util/orderValidators/prepareOrder.js index d378d8756b7..82f43ef3f79 100644 --- a/packages/api-plugin-orders/src/util/orderValidators/prepareOrder.js +++ b/packages/api-plugin-orders/src/util/orderValidators/prepareOrder.js @@ -184,13 +184,15 @@ export default async function prepareOrder(context, input, flag) { const allValidateFuncs = context.getFunctionsOfType("validateOrderMethods"); // Collect results from validation specific to each fulfillment-method - for (const group of finalFulfillmentGroups) { - const requiredMethodFunctionName = `validateOrderMethods${group.shipmentMethod.name}`; - const requiredMethodFunction = allValidateFuncs.find((fn) => fn.name === requiredMethodFunctionName); + if (allValidateFuncs && Array.isArray(allValidateFuncs) && allValidateFuncs.length) { + for (const group of finalFulfillmentGroups) { + const requiredMethodFunctionObject = allValidateFuncs.find((fn) => fn.key === group.shipmentMethod.name); + const requiredMethodFunction = requiredMethodFunctionObject ? requiredMethodFunctionObject.handler : undefined; - if (requiredMethodFunction) { - // eslint-disable-next-line no-await-in-loop - await requiredMethodFunction(context, orderInput, validationResults); + if (requiredMethodFunction) { + // eslint-disable-next-line no-await-in-loop + await requiredMethodFunction(context, orderInput, validationResults); + } } } diff --git a/packages/api-plugin-orders/src/util/orderValidators/validateInitialOrderData.js b/packages/api-plugin-orders/src/util/orderValidators/validateInitialOrderData.js index 8d673920318..ddb7f55c8c0 100644 --- a/packages/api-plugin-orders/src/util/orderValidators/validateInitialOrderData.js +++ b/packages/api-plugin-orders/src/util/orderValidators/validateInitialOrderData.js @@ -10,8 +10,7 @@ import ReactionError from "@reactioncommerce/reaction-error"; export default async function validateInitialOrderData(context, cleanedInput) { const { order: orderInput } = cleanedInput; const { cartId, shopId } = orderInput; - const { collections, userId } = context; - const { Cart } = collections; + const { collections: { Cart }, userId } = context; if (!shopId) throw new ReactionError("invalid-param", "ShopID not found in order data", { field: "ShopId", value: shopId }); From adf38b0f50529c9d5f0bcad862723ba5f2549d77 Mon Sep 17 00:00:00 2001 From: Sujith Date: Thu, 3 Nov 2022 19:20:19 +0530 Subject: [PATCH 37/71] fix: export getCartById as query Signed-off-by: Sujith --- .../src/queries/getCartById.js | 17 + .../api-plugin-carts/src/queries/index.js | 2 + .../src/util/getCartById.test.js | 46 +++ pnpm-lock.yaml | 362 ++++++++++++------ 4 files changed, 313 insertions(+), 114 deletions(-) create mode 100644 packages/api-plugin-carts/src/queries/getCartById.js create mode 100644 packages/api-plugin-carts/src/util/getCartById.test.js diff --git a/packages/api-plugin-carts/src/queries/getCartById.js b/packages/api-plugin-carts/src/queries/getCartById.js new file mode 100644 index 00000000000..845e7793438 --- /dev/null +++ b/packages/api-plugin-carts/src/queries/getCartById.js @@ -0,0 +1,17 @@ +import getCartByIdUtil from "../util/getCartById.js"; +/** + * @name getCartById + * @method + * @memberof Cart + * @summary Gets a cart from the db by ID. If there is an account for the request, verifies that the + * account has permission to access the cart. Optionally throws an error if not found. + * @param {Object} context - an object containing the per-request state + * @param {String} cartId The cart ID + * @param {Object} [options] Options + * @param {String} [options.cartToken] Cart token, required if it's an anonymous cart + * @param {Boolean} [options.throwIfNotFound] Default false. Throw a not-found error rather than return null `cart` + * @returns {Object|null} The cart document, or null if not found and `throwIfNotFound` was false + */ +export default async function getCartById(context, cartId, { cartToken, throwIfNotFound = false } = {}) { + return getCartByIdUtil(context, cartId, { cartToken, throwIfNotFound }); +} diff --git a/packages/api-plugin-carts/src/queries/index.js b/packages/api-plugin-carts/src/queries/index.js index 4c77c918431..7693c62f8ab 100644 --- a/packages/api-plugin-carts/src/queries/index.js +++ b/packages/api-plugin-carts/src/queries/index.js @@ -1,9 +1,11 @@ import accountCartByAccountId from "./accountCartByAccountId.js"; import anonymousCartByCartId from "./anonymousCartByCartId.js"; +import getCartById from "./getCartById.js"; import getCommonOrderForCartGroup from "./getCommonOrderForCartGroup.js"; export default { accountCartByAccountId, anonymousCartByCartId, + getCartById, getCommonOrderForCartGroup }; diff --git a/packages/api-plugin-carts/src/util/getCartById.test.js b/packages/api-plugin-carts/src/util/getCartById.test.js new file mode 100644 index 00000000000..a2ffdeda808 --- /dev/null +++ b/packages/api-plugin-carts/src/util/getCartById.test.js @@ -0,0 +1,46 @@ +import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; +import ReactionError from "@reactioncommerce/reaction-error"; +import getCartById from "./getCartById.js"; + +test("should throw when cart not found and throwIfNotFound is true", async () => { + const cartId = "123"; + const cartToken = "xyz"; + mockContext.collections.Cart.findOne.mockReturnValueOnce(null); + const expectedError = new ReactionError("not-found", "Cart not found"); + await expect(getCartById(mockContext, cartId, { cartToken, throwIfNotFound: true })).rejects.toThrow(expectedError); +}); + +test("should return null when cart not found and throwIfNotFound is false", async () => { + const cartId = "123"; + const cartToken = "xyz"; + mockContext.collections.Cart.findOne.mockReturnValueOnce(null); + await expect(getCartById(mockContext, cartId, { cartToken, throwIfNotFound: false })).toMatchObject({}); +}); + +test("should throw when cart found but accountId does not match", async () => { + const cartId = "123"; + const cartToken = "xyz"; + mockContext.accountId = "accountId123"; + const cart = { + _id: cartId, + anonymousAccessToken: cartToken, + accountId: "accountId456" + }; + mockContext.collections.Cart.findOne.mockReturnValueOnce(Promise.resolve(cart)); + const expectedError = new ReactionError("access-denied", "Access Denied"); + await expect(getCartById(mockContext, cartId, { cartToken, throwIfNotFound: true })).rejects.toThrow(expectedError); +}); + +test("should return cart when cart found and accountId matches", async () => { + const cartId = "123"; + const cartToken = "xyz"; + mockContext.accountId = "accountId123"; + const cart = { + _id: cartId, + anonymousAccessToken: cartToken, + accountId: "accountId123" + }; + mockContext.collections.Cart.findOne.mockReturnValueOnce(Promise.resolve(cart)); + const result = await getCartById(mockContext, cartId, { cartToken, throwIfNotFound: true }); + expect(result).toMatchObject(cart); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a54914d1c58..6d4218cb158 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -225,20 +225,20 @@ importers: '@reactioncommerce/api-plugin-payments-example': link:../../packages/api-plugin-payments-example '@reactioncommerce/api-plugin-payments-stripe-sca': link:../../packages/api-plugin-payments-stripe-sca '@reactioncommerce/api-plugin-pricing-simple': link:../../packages/api-plugin-pricing-simple - '@reactioncommerce/api-plugin-products': link:../../packages/api-plugin-products + '@reactioncommerce/api-plugin-products': 1.3.1_graphql@14.7.0 '@reactioncommerce/api-plugin-settings': link:../../packages/api-plugin-settings '@reactioncommerce/api-plugin-shipments': link:../../packages/api-plugin-shipments - '@reactioncommerce/api-plugin-shipments-flat-rate': link:../../packages/api-plugin-shipments-flat-rate + '@reactioncommerce/api-plugin-shipments-flat-rate': 1.0.10_graphql@14.7.0 '@reactioncommerce/api-plugin-shops': link:../../packages/api-plugin-shops '@reactioncommerce/api-plugin-simple-schema': link:../../packages/api-plugin-simple-schema '@reactioncommerce/api-plugin-sitemap-generator': link:../../packages/api-plugin-sitemap-generator - '@reactioncommerce/api-plugin-surcharges': link:../../packages/api-plugin-surcharges + '@reactioncommerce/api-plugin-surcharges': 1.1.8_graphql@14.7.0 '@reactioncommerce/api-plugin-system-information': link:../../packages/api-plugin-system-information '@reactioncommerce/api-plugin-tags': link:../../packages/api-plugin-tags '@reactioncommerce/api-plugin-taxes': link:../../packages/api-plugin-taxes '@reactioncommerce/api-plugin-taxes-flat-rate': link:../../packages/api-plugin-taxes-flat-rate '@reactioncommerce/api-plugin-translations': link:../../packages/api-plugin-translations - '@reactioncommerce/api-utils': link:../../packages/api-utils + '@reactioncommerce/api-utils': 1.17.0_graphql@14.7.0 '@reactioncommerce/db-version-check': link:../../packages/db-version-check '@reactioncommerce/file-collections': link:../../packages/file-collections '@reactioncommerce/file-collections-sa-gridfs': link:../../packages/file-collections-sa-gridfs @@ -297,7 +297,7 @@ importers: dependencies: '@apollo/federation': 0.15.1_graphql@14.7.0 '@graphql-tools/merge': 8.3.6_graphql@14.7.0 - '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/api-utils': 1.17.0_graphql@14.7.0 '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error @@ -349,7 +349,7 @@ importers: ramda: ^0.28.0 simpl-schema: ^1.8.0 dependencies: - '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/api-utils': 1.17.0_graphql@14.7.0 '@reactioncommerce/db-version-check': link:../db-version-check '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random @@ -377,7 +377,7 @@ importers: lodash: ^4.17.15 simpl-schema: ^1.12.0 dependencies: - '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error lodash: 4.17.21 @@ -389,7 +389,7 @@ importers: '@reactioncommerce/babel-remove-es-create-require': ~1.0.0 faker: ^4.1.0 dependencies: - '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 faker: 4.1.0 devDependencies: '@reactioncommerce/babel-remove-es-create-require': 1.0.0_@babel+core@7.19.0 @@ -413,18 +413,18 @@ importers: node-fetch: ^2.6.0 simpl-schema: ^1.5.7 dependencies: - '@accounts/graphql-api': 0.33.1_2czzfrymqvhuggs422aynrtswq + '@accounts/graphql-api': 0.33.1_snjxfcqg4vfu4wt5s37iqh5esu '@accounts/magic-link': 0.1.1_@accounts+server@0.33.1 '@accounts/mongo': 0.33.5 '@accounts/password': 0.32.2_@accounts+server@0.33.1 '@accounts/server': 0.33.1 - '@graphql-modules/core': 0.7.17_47dpehqa77wbfro2ktaldhryyy + '@graphql-modules/core': 0.7.17_reflect-metadata@0.1.13 '@reactioncommerce/api-core': link:../api-core - '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/api-utils': 1.17.0 '@reactioncommerce/logger': link:../logger '@reactioncommerce/reaction-error': link:../reaction-error envalid: 6.0.2 - graphql-tag: 2.12.6_graphql@14.7.0 + graphql-tag: 2.12.6 jwt-decode: 3.1.2 mongoose: 6.6.0 node-fetch: 2.6.7 @@ -448,7 +448,7 @@ importers: lodash: ^4.17.15 ramda: ^0.28.0 dependencies: - '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 '@reactioncommerce/db-version-check': link:../db-version-check '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random @@ -486,7 +486,7 @@ importers: dependencies: '@reactioncommerce/api-plugin-address-validation': link:../api-plugin-address-validation '@reactioncommerce/api-plugin-tags': link:../api-plugin-tags - '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error @@ -520,7 +520,7 @@ importers: object-hash: ^2.0.3 simpl-schema: ^1.12.0 dependencies: - '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error @@ -543,7 +543,7 @@ importers: '@reactioncommerce/babel-remove-es-create-require': ~1.0.0 '@reactioncommerce/data-factory': ~1.0.1 dependencies: - '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 devDependencies: '@reactioncommerce/babel-remove-es-create-require': 1.0.0_@babel+core@7.19.0 '@reactioncommerce/data-factory': 1.0.1 @@ -563,7 +563,7 @@ importers: babel-plugin-transform-import-meta: ~1.0.0 simpl-schema: ^1.12.0 dependencies: - '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error @@ -585,7 +585,7 @@ importers: '@reactioncommerce/db-version-check': ^1.0.0 '@reactioncommerce/logger': ^1.1.3 dependencies: - '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 '@reactioncommerce/db-version-check': link:../db-version-check '@reactioncommerce/logger': link:../logger devDependencies: @@ -608,7 +608,7 @@ importers: envalid: ^6.0.2 simpl-schema: ^1.12.0 dependencies: - '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 '@reactioncommerce/logger': link:../logger '@reactioncommerce/nodemailer': 5.0.5 '@reactioncommerce/reaction-error': link:../reaction-error @@ -634,7 +634,7 @@ importers: handlebars: ^4.7.6 simpl-schema: ^1.12.0 dependencies: - '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error @@ -659,7 +659,7 @@ importers: sharp: ^0.30.7 simpl-schema: ~1.10.2 dependencies: - '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 '@reactioncommerce/file-collections': link:../file-collections '@reactioncommerce/file-collections-sa-gridfs': link:../file-collections-sa-gridfs '@reactioncommerce/logger': link:../logger @@ -684,7 +684,7 @@ importers: lodash: ^4.17.21 simpl-schema: ^1.12.2 dependencies: - '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error @@ -694,46 +694,6 @@ importers: '@reactioncommerce/api-plugin-carts': link:../api-plugin-carts '@reactioncommerce/data-factory': 1.0.1 - packages/api-plugin-fulfillment-method-shipping-flat-rate: - specifiers: - '@reactioncommerce/api-utils': ^1.16.9 - '@reactioncommerce/logger': ^1.1.3 - '@reactioncommerce/random': ~1.0.2 - '@reactioncommerce/reaction-error': ^1.0.1 - simpl-schema: ^1.12.2 - dependencies: - '@reactioncommerce/api-utils': link:../api-utils - '@reactioncommerce/logger': link:../logger - '@reactioncommerce/random': link:../random - '@reactioncommerce/reaction-error': link:../reaction-error - simpl-schema: 1.12.3 - - packages/api-plugin-fulfillment-method-shipping-ups: - specifiers: - '@reactioncommerce/api-utils': ^1.16.9 - '@reactioncommerce/reaction-error': ^1.0.1 - simpl-schema: ^1.12.2 - dependencies: - '@reactioncommerce/api-utils': link:../api-utils - '@reactioncommerce/reaction-error': link:../reaction-error - simpl-schema: 1.12.3 - - packages/api-plugin-fulfillment-type-shipping: - specifiers: - '@reactioncommerce/api-utils': ^1.16.9 - '@reactioncommerce/logger': ^1.1.3 - '@reactioncommerce/random': ~1.0.2 - '@reactioncommerce/reaction-error': ^1.0.1 - lodash: ^4.17.21 - simpl-schema: ^1.12.2 - dependencies: - '@reactioncommerce/api-utils': link:../api-utils - '@reactioncommerce/logger': link:../logger - '@reactioncommerce/random': link:../random - '@reactioncommerce/reaction-error': link:../reaction-error - lodash: 4.17.21 - simpl-schema: 1.12.3 - packages/api-plugin-i18n: specifiers: '@reactioncommerce/api-utils': ^1.16.5 @@ -741,7 +701,7 @@ importers: '@reactioncommerce/data-factory': ~1.0.1 lodash: ~4.17.15 dependencies: - '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 lodash: 4.17.21 devDependencies: '@reactioncommerce/babel-remove-es-create-require': 1.0.0_@babel+core@7.19.0 @@ -761,7 +721,7 @@ importers: babel-plugin-transform-import-meta: ~1.0.0 lodash: ~4.17.21 dependencies: - '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 '@reactioncommerce/reaction-error': link:../reaction-error lodash: 4.17.21 devDependencies: @@ -790,7 +750,7 @@ importers: lodash: ^4.17.15 simpl-schema: ^1.12.0 dependencies: - '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error @@ -816,7 +776,7 @@ importers: later: ^1.2.0 simpl-schema: ^1.12.0 dependencies: - '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random envalid: 6.0.2 @@ -841,7 +801,7 @@ importers: lodash: ^4.17.15 simpl-schema: ^1.12.0 dependencies: - '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error lodash: 4.17.21 @@ -864,7 +824,7 @@ importers: '@reactioncommerce/random': ^1.0.2 simpl-schema: ^1.12.0 dependencies: - '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random simpl-schema: 1.12.3 @@ -891,7 +851,7 @@ importers: lodash: ^4.17.21 simpl-schema: ^1.5.9 dependencies: - '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error @@ -923,7 +883,7 @@ importers: babel-plugin-transform-import-meta: ~1.0.0 simpl-schema: ^1.12.0 dependencies: - '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 '@reactioncommerce/reaction-error': link:../reaction-error simpl-schema: 1.12.3 devDependencies: @@ -948,7 +908,7 @@ importers: babel-plugin-transform-es2015-modules-commonjs: ^6.26.2 babel-plugin-transform-import-meta: ~1.0.0 dependencies: - '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 '@reactioncommerce/random': link:../random devDependencies: '@babel/core': 7.19.0 @@ -978,7 +938,7 @@ importers: stripe: ^8.222.0 dependencies: '@reactioncommerce/api-plugin-carts': link:../api-plugin-carts - '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error @@ -1010,7 +970,7 @@ importers: ramda: ^0.28.0 simpl-schema: ^1.12.0 dependencies: - '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 '@reactioncommerce/logger': link:../logger '@reactioncommerce/reaction-error': link:../reaction-error accounting-js: 1.1.1 @@ -1041,7 +1001,7 @@ importers: lodash: 4.17.21 simpl-schema: ^1.12.0 dependencies: - '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error @@ -1071,7 +1031,7 @@ importers: babel-plugin-transform-import-meta: ~1.0.0 envalid: ^7.3.1 dependencies: - '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 '@reactioncommerce/file-collections': link:../file-collections '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random @@ -1095,7 +1055,7 @@ importers: '@reactioncommerce/reaction-error': ~1.0.1 simpl-schema: ~1.12.0 dependencies: - '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 '@reactioncommerce/reaction-error': link:../reaction-error simpl-schema: 1.12.3 devDependencies: @@ -1120,7 +1080,7 @@ importers: lodash: ^4.17.21 simpl-schema: ^1.5.7 dependencies: - '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 '@reactioncommerce/logger': link:../logger '@reactioncommerce/reaction-error': link:../reaction-error lodash: 4.17.21 @@ -1151,7 +1111,7 @@ importers: lodash: ^4.17.15 simpl-schema: ^1.12.0 dependencies: - '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error @@ -1183,7 +1143,7 @@ importers: simpl-schema: ~1.12.0 dependencies: '@reactioncommerce/api-plugin-address-validation': link:../api-plugin-address-validation - '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error @@ -1203,7 +1163,7 @@ importers: '@reactioncommerce/babel-remove-es-create-require': ~1.0.0 '@reactioncommerce/data-factory': ~1.0.1 dependencies: - '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 devDependencies: '@reactioncommerce/babel-remove-es-create-require': 1.0.0_@babel+core@7.19.0 '@reactioncommerce/data-factory': 1.0.1 @@ -1215,7 +1175,7 @@ importers: '@reactioncommerce/reaction-error': ^1.0.1 simpl-schema: ^1.12.0 dependencies: - '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 '@reactioncommerce/logger': link:../logger '@reactioncommerce/reaction-error': link:../reaction-error simpl-schema: 1.12.3 @@ -1234,7 +1194,7 @@ importers: babel-plugin-transform-import-meta: ~1.0.0 simpl-schema: ^1.12.0 dependencies: - '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error simpl-schema: 1.12.3 @@ -1260,7 +1220,7 @@ importers: babel-plugin-transform-es2015-modules-commonjs: ^6.26.2 babel-plugin-transform-import-meta: ~1.0.0 dependencies: - '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 '@reactioncommerce/logger': link:../logger '@reactioncommerce/reaction-error': link:../reaction-error devDependencies: @@ -1289,7 +1249,7 @@ importers: lodash: ~4.17.21 simpl-schema: ~1.10.2 dependencies: - '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 '@reactioncommerce/data-factory': 1.0.1 '@reactioncommerce/db-version-check': link:../db-version-check '@reactioncommerce/file-collections': link:../file-collections @@ -1323,7 +1283,7 @@ importers: lodash: ^4.17.15 simpl-schema: ^1.12.0 dependencies: - '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 '@reactioncommerce/logger': link:../logger '@reactioncommerce/reaction-error': link:../reaction-error accounting-js: 1.1.1 @@ -1349,7 +1309,7 @@ importers: '@reactioncommerce/reaction-error': ^1.0.1 simpl-schema: ^1.12.0 dependencies: - '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error simpl-schema: 1.12.3 @@ -1365,7 +1325,7 @@ importers: '@reactioncommerce/babel-remove-es-create-require': ~1.0.0 '@reactioncommerce/data-factory': ~1.0.1 dependencies: - '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 devDependencies: '@babel/core': 7.19.0 '@babel/preset-env': 7.19.0_@babel+core@7.19.0 @@ -1573,7 +1533,7 @@ importers: packages: - /@accounts/graphql-api/0.33.1_2czzfrymqvhuggs422aynrtswq: + /@accounts/graphql-api/0.33.1_snjxfcqg4vfu4wt5s37iqh5esu: resolution: {integrity: sha512-wvseq3lCOPTcfao4VYs0v+dABJV+j1XTOHs8LS37I6Uk7/6jMkBlPlWKptdREEOnPM+b5rK7YiGpSFAC2LalPg==} peerDependencies: '@accounts/magic-link': ^0.1.0 @@ -1588,11 +1548,10 @@ packages: '@accounts/password': 0.32.2_@accounts+server@0.33.1 '@accounts/server': 0.33.1 '@accounts/types': 0.33.2 - '@graphql-modules/core': 0.7.17_47dpehqa77wbfro2ktaldhryyy - '@graphql-tools/merge': 6.2.13_graphql@14.7.0 - '@graphql-tools/utils': 7.9.0_graphql@14.7.0 - graphql: 14.7.0 - graphql-tag: 2.12.6_graphql@14.7.0 + '@graphql-modules/core': 0.7.17_reflect-metadata@0.1.13 + '@graphql-tools/merge': 6.2.13 + '@graphql-tools/utils': 7.9.0 + graphql-tag: 2.12.6 request-ip: 2.1.3 tslib: 2.3.0 dev: false @@ -4043,18 +4002,17 @@ packages: - supports-color dev: false - /@graphql-modules/core/0.7.17_47dpehqa77wbfro2ktaldhryyy: + /@graphql-modules/core/0.7.17_reflect-metadata@0.1.13: resolution: {integrity: sha512-hGJa1VIsIHTKJ0Hc5gJfFrdhHAF1Vm+LWYeMNC5mPbVd/IZA1wVSpdLDjjliylLI6GnVRu1YreS2gXvFNXPJFA==} peerDependencies: graphql: ^14.1.1 || ^15.0.0 dependencies: '@graphql-modules/di': 0.7.17_reflect-metadata@0.1.13 - '@graphql-toolkit/common': 0.10.6_graphql@14.7.0 - '@graphql-toolkit/schema-merging': 0.10.6_graphql@14.7.0 + '@graphql-toolkit/common': 0.10.6 + '@graphql-toolkit/schema-merging': 0.10.6 apollo-server-caching: 0.5.1 deepmerge: 4.2.2 - graphql: 14.7.0 - graphql-tools: 4.0.5_graphql@14.7.0 + graphql-tools: 4.0.5 tslib: 2.0.0 transitivePeerDependencies: - reflect-metadata @@ -4070,7 +4028,7 @@ packages: tslib: 2.0.0 dev: false - /@graphql-toolkit/common/0.10.6_graphql@14.7.0: + /@graphql-toolkit/common/0.10.6: resolution: {integrity: sha512-rrH/KPheh/wCZzqUmNayBHd+aNWl/751C4iTL/327TzONdAVrV7ZQOyEkpGLW6YEFWPIlWxNkaBoEALIjCxTGg==} deprecated: GraphQL Toolkit is deprecated and merged into GraphQL Tools, so it will no longer get updates. Use GraphQL Tools instead to stay up-to-date! Check out https://www.graphql-tools.com/docs/migration-from-toolkit for migration and https://the-guild.dev/blog/graphql-tools-v6 for new changes. peerDependencies: @@ -4078,31 +4036,28 @@ packages: dependencies: aggregate-error: 3.0.1 camel-case: 4.1.1 - graphql: 14.7.0 - graphql-tools: 4.0.5_graphql@14.7.0 + graphql-tools: 4.0.5 lodash: 4.17.15 dev: false - /@graphql-toolkit/schema-merging/0.10.6_graphql@14.7.0: + /@graphql-toolkit/schema-merging/0.10.6: resolution: {integrity: sha512-BNABgYaNCw4Li3EiH/x7oDpkN+ml3M0SWqjnsW1Pf2NcyfGlv033Bda+O/q4XYtseZ0OOOh52GLXtUgwyPFb8A==} peerDependencies: graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 dependencies: - '@graphql-toolkit/common': 0.10.6_graphql@14.7.0 + '@graphql-toolkit/common': 0.10.6 deepmerge: 4.2.2 - graphql: 14.7.0 - graphql-tools: 4.0.5_graphql@14.7.0 + graphql-tools: 4.0.5 tslib: 1.11.1 dev: false - /@graphql-tools/merge/6.2.13_graphql@14.7.0: + /@graphql-tools/merge/6.2.13: resolution: {integrity: sha512-Qjlki0fp+bBQPinhdv7rv24eurvThZ5oIFvGMpLxMZplbw/ovJ2c6llwXr5PCuWAk9HGZsyM9NxxDgtTRfq3dQ==} peerDependencies: graphql: ^14.0.0 || ^15.0.0 dependencies: - '@graphql-tools/schema': 7.1.5_graphql@14.7.0 - '@graphql-tools/utils': 7.9.0_graphql@14.7.0 - graphql: 14.7.0 + '@graphql-tools/schema': 7.1.5 + '@graphql-tools/utils': 7.9.0 tslib: 2.2.0 dev: false @@ -4116,25 +4071,23 @@ packages: tslib: 2.4.0 dev: false - /@graphql-tools/schema/7.1.5_graphql@14.7.0: + /@graphql-tools/schema/7.1.5: resolution: {integrity: sha512-uyn3HSNSckf4mvQSq0Q07CPaVZMNFCYEVxroApOaw802m9DcZPgf9XVPy/gda5GWj9AhbijfRYVTZQgHnJ4CXA==} peerDependencies: graphql: ^14.0.0 || ^15.0.0 dependencies: - '@graphql-tools/utils': 7.9.0_graphql@14.7.0 - graphql: 14.7.0 + '@graphql-tools/utils': 7.9.0 tslib: 2.2.0 value-or-promise: 1.0.6 dev: false - /@graphql-tools/utils/7.9.0_graphql@14.7.0: + /@graphql-tools/utils/7.9.0: resolution: {integrity: sha512-WaYfdKmYFw7Rw5BmFehwo5zqoHNlO1soKVSdrh4qN0X1U34g4aqESAMYogtlp2yWDb2b3qKShiByCvRWNypbVA==} peerDependencies: graphql: ^14.0.0 || ^15.0.0 dependencies: '@ardatan/aggregate-error': 0.0.6 camel-case: 4.1.2 - graphql: 14.7.0 tslib: 2.2.0 dev: false @@ -4654,6 +4607,106 @@ packages: /@protobufjs/utf8/1.1.0: resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + /@reactioncommerce/api-plugin-products/1.3.1_graphql@14.7.0: + resolution: {integrity: sha512-lnjj9QkRlibsBUOZAgGNi9S13dbZogTouaZ59/+fiIlYJDrSOJ1x4yylGdWMZ6W7K2PvWoZvXk1YrzGopnCflQ==} + engines: {node: '>=14.18.1'} + dependencies: + '@reactioncommerce/api-utils': 1.17.0_graphql@14.7.0 + '@reactioncommerce/logger': 1.1.5 + '@reactioncommerce/random': 1.0.2 + '@reactioncommerce/reaction-error': 1.0.1 + lodash: 4.17.21 + simpl-schema: 1.12.3 + transitivePeerDependencies: + - graphql + dev: false + + /@reactioncommerce/api-plugin-shipments-flat-rate/1.0.10_graphql@14.7.0: + resolution: {integrity: sha512-XilxXESaBQa6v1ChSTduYg8kCTYElI9jy6lWevrfjkRjquSlA/sNZmZOMSkRW0tdZhZMCnAboH+C2r/62IDHBg==} + engines: {node: '>=14.18.1'} + dependencies: + '@reactioncommerce/api-utils': 1.17.0_graphql@14.7.0 + '@reactioncommerce/logger': 1.1.5 + '@reactioncommerce/random': 1.0.2 + '@reactioncommerce/reaction-error': 1.0.1 + lodash: 4.17.21 + simpl-schema: 1.12.3 + transitivePeerDependencies: + - graphql + dev: false + + /@reactioncommerce/api-plugin-surcharges/1.1.8_graphql@14.7.0: + resolution: {integrity: sha512-rPuZP4OswJlN28AU/TctLMVoNzLnwQZJWSLVXvJpwMZD9QOxHc40IjSazP5sMd52sMX/h4+PtYjMluIiNmGJ3g==} + engines: {node: '>=14.18.1'} + dependencies: + '@reactioncommerce/api-utils': 1.17.0_graphql@14.7.0 + '@reactioncommerce/random': 1.0.2 + '@reactioncommerce/reaction-error': 1.0.1 + simpl-schema: 1.12.3 + transitivePeerDependencies: + - graphql + dev: false + + /@reactioncommerce/api-utils/1.17.0: + resolution: {integrity: sha512-AJ78ytkirkbwPqp/WyctWAkk23Y7byenUT7kZ0uAuDIDcDPWHc0Qv6NgwNF3eaHpoZT08IwkycAp+0zFnPt0TA==} + engines: {node: '>=14.18.1'} + dependencies: + '@jest/globals': 26.6.2 + '@reactioncommerce/logger': 1.1.5 + '@reactioncommerce/random': 1.0.2 + '@reactioncommerce/reaction-error': 1.0.1 + accounting-js: 1.1.1 + callsite: 1.0.0 + envalid: 6.0.2 + graphql-fields: 2.0.3 + graphql-relay: 0.9.0 + lodash: 4.17.21 + ramda: 0.28.0 + transliteration: 2.3.5 + transitivePeerDependencies: + - graphql + dev: false + + /@reactioncommerce/api-utils/1.17.0_graphql@14.7.0: + resolution: {integrity: sha512-AJ78ytkirkbwPqp/WyctWAkk23Y7byenUT7kZ0uAuDIDcDPWHc0Qv6NgwNF3eaHpoZT08IwkycAp+0zFnPt0TA==} + engines: {node: '>=14.18.1'} + dependencies: + '@jest/globals': 26.6.2 + '@reactioncommerce/logger': 1.1.5 + '@reactioncommerce/random': 1.0.2 + '@reactioncommerce/reaction-error': 1.0.1 + accounting-js: 1.1.1 + callsite: 1.0.0 + envalid: 6.0.2 + graphql-fields: 2.0.3 + graphql-relay: 0.9.0_graphql@14.7.0 + lodash: 4.17.21 + ramda: 0.28.0 + transliteration: 2.3.5 + transitivePeerDependencies: + - graphql + dev: false + + /@reactioncommerce/api-utils/1.17.0_graphql@15.8.0: + resolution: {integrity: sha512-AJ78ytkirkbwPqp/WyctWAkk23Y7byenUT7kZ0uAuDIDcDPWHc0Qv6NgwNF3eaHpoZT08IwkycAp+0zFnPt0TA==} + engines: {node: '>=14.18.1'} + dependencies: + '@jest/globals': 26.6.2 + '@reactioncommerce/logger': 1.1.5 + '@reactioncommerce/random': 1.0.2 + '@reactioncommerce/reaction-error': 1.0.1 + accounting-js: 1.1.1 + callsite: 1.0.0 + envalid: 6.0.2 + graphql-fields: 2.0.3 + graphql-relay: 0.9.0_graphql@15.8.0 + lodash: 4.17.21 + ramda: 0.28.0 + transliteration: 2.3.5 + transitivePeerDependencies: + - graphql + dev: false + /@reactioncommerce/babel-remove-es-create-require/1.0.0_@babel+core@7.19.0: resolution: {integrity: sha512-yR1vMj76hK5D9/VcXjFpk2OMoYpQdbvEgGcJ79cEzTL/jkFwIs53Zl6PNBjcC608P8qHl5eypEYRwG7CCGLzwQ==} peerDependencies: @@ -4709,11 +4762,27 @@ packages: eslint-plugin-you-dont-need-lodash-underscore: 6.12.0 dev: true + /@reactioncommerce/logger/1.1.5: + resolution: {integrity: sha512-fG6wohv9NoX6ZjNzs0YcFRFxHyndO4VitMCrbz+8HrnM7LNJtQRzrUcR58RptagYixZ4GyiHY6WhmIfrMpNvLw==} + dependencies: + bunyan: 1.8.15 + bunyan-format: 0.2.1 + node-loggly-bulk: 2.2.5 + dev: false + /@reactioncommerce/nodemailer/5.0.5: resolution: {integrity: sha512-u4ontTETlROmLglkMDyouMXlX62NXOGfOUAd75Ilk3W4tcsRjRXX+g5C5B4mBCCcJB0wHn1yh/a4pOYkn81vUQ==} engines: {node: '>=4.0.0'} dev: false + /@reactioncommerce/random/1.0.2: + resolution: {integrity: sha512-02eARZh6FQ45tEXXZUwnz90XWQasLpnV140CcuOl/L3dhEL4Emu2oCZemVhKvNb2JMmm5vOG2Y0CUb12xAx73w==} + dev: false + + /@reactioncommerce/reaction-error/1.0.1: + resolution: {integrity: sha512-l1HyDTFw4m1j8EPWNA/bbF2GZZaLYQjwmgniSjjM1cgnhfjCqeLywi501oPkmH8E84Jh0EH+4ADhNCcZ4m4gug==} + dev: false + /@sinclair/typebox/0.24.41: resolution: {integrity: sha512-TJCgQurls4FipFvHeC+gfAzb+GGstL0TDwYJKQVtTeSvJIznWzP7g3bAd5gEBlr8+bIxqnWS9VGVWREDhmE8jA==} dev: true @@ -5378,6 +5447,17 @@ packages: tslib: 1.14.1 dev: true + /apollo-link/1.2.14: + resolution: {integrity: sha512-p67CMEFP7kOG1JZ0ZkYZwRDa369w5PIjtMjvrQd/HnIV8FRsHRqLqK+oAZQnFa1DDdZtOtHTi+aMIW6EatC2jg==} + peerDependencies: + graphql: ^0.11.3 || ^0.12.3 || ^0.13.0 || ^14.0.0 || ^15.0.0 + dependencies: + apollo-utilities: 1.3.4 + ts-invariant: 0.4.4 + tslib: 1.14.1 + zen-observable-ts: 0.8.21 + dev: false + /apollo-link/1.2.14_graphql@14.7.0: resolution: {integrity: sha512-p67CMEFP7kOG1JZ0ZkYZwRDa369w5PIjtMjvrQd/HnIV8FRsHRqLqK+oAZQnFa1DDdZtOtHTi+aMIW6EatC2jg==} peerDependencies: @@ -5586,6 +5666,17 @@ packages: transitivePeerDependencies: - encoding + /apollo-utilities/1.3.4: + resolution: {integrity: sha512-pk2hiWrCXMAy2fRPwEyhvka+mqwzeP60Jr1tRYi5xru+3ko94HI9o6lK0CT33/w4RDlxWchmdhDCrvdr+pHCig==} + peerDependencies: + graphql: ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 + dependencies: + '@wry/equality': 0.1.11 + fast-json-stable-stringify: 2.1.0 + ts-invariant: 0.4.4 + tslib: 1.14.1 + dev: false + /apollo-utilities/1.3.4_graphql@14.7.0: resolution: {integrity: sha512-pk2hiWrCXMAy2fRPwEyhvka+mqwzeP60Jr1tRYi5xru+3ko94HI9o6lK0CT33/w4RDlxWchmdhDCrvdr+pHCig==} peerDependencies: @@ -9067,6 +9158,13 @@ packages: graphql: 14.7.0 dev: false + /graphql-relay/0.9.0: + resolution: {integrity: sha512-yNJLCqcjz0XpzpmmckRJCSK8a2ZLwTurwrQ09UyGftONh52PbrGpK1UO4yspvj0c7pC+jkN4ZUqVXG3LRrWkXQ==} + engines: {node: ^12.20.0 || ^14.15.0 || >= 15.9.0} + peerDependencies: + graphql: ^15.5.3 + dev: false + /graphql-relay/0.9.0_graphql@14.7.0: resolution: {integrity: sha512-yNJLCqcjz0XpzpmmckRJCSK8a2ZLwTurwrQ09UyGftONh52PbrGpK1UO4yspvj0c7pC+jkN4ZUqVXG3LRrWkXQ==} engines: {node: ^12.20.0 || ^14.15.0 || >= 15.9.0} @@ -9076,6 +9174,15 @@ packages: graphql: 14.7.0 dev: false + /graphql-relay/0.9.0_graphql@15.8.0: + resolution: {integrity: sha512-yNJLCqcjz0XpzpmmckRJCSK8a2ZLwTurwrQ09UyGftONh52PbrGpK1UO4yspvj0c7pC+jkN4ZUqVXG3LRrWkXQ==} + engines: {node: ^12.20.0 || ^14.15.0 || >= 15.9.0} + peerDependencies: + graphql: ^15.5.3 + dependencies: + graphql: 15.8.0 + dev: false + /graphql-schema-linter/3.0.0_graphql@16.6.0: resolution: {integrity: sha512-nXsyFvcqrD9/QWgimvJjvRBsUXHw8O7XoPLhpnGZd9eX6r2ZXsOBCGhTzF9ZrFtWosCBEfBGU2xpFUPe0qCHVg==} hasBin: true @@ -9099,6 +9206,15 @@ packages: iterall: 1.3.0 dev: false + /graphql-tag/2.12.6: + resolution: {integrity: sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==} + engines: {node: '>=10'} + peerDependencies: + graphql: ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + dependencies: + tslib: 2.4.0 + dev: false + /graphql-tag/2.12.6_graphql@14.7.0: resolution: {integrity: sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==} engines: {node: '>=10'} @@ -9108,6 +9224,19 @@ packages: graphql: 14.7.0 tslib: 2.4.0 + /graphql-tools/4.0.5: + resolution: {integrity: sha512-kQCh3IZsMqquDx7zfIGWBau42xe46gmqabwYkpPlCLIjcEY1XK+auP7iGRD9/205BPyoQdY8hT96MPpgERdC9Q==} + deprecated: This package has been deprecated and now it only exports makeExecutableSchema.\nAnd it will no longer receive updates.\nWe recommend you to migrate to scoped packages such as @graphql-tools/schema, @graphql-tools/utils and etc.\nCheck out https://www.graphql-tools.com to learn what package you should use instead + peerDependencies: + graphql: ^0.13.0 || ^14.0.0 + dependencies: + apollo-link: 1.2.14 + apollo-utilities: 1.3.4 + deprecated-decorator: 0.1.6 + iterall: 1.3.0 + uuid: 3.4.0 + dev: false + /graphql-tools/4.0.5_graphql@14.7.0: resolution: {integrity: sha512-kQCh3IZsMqquDx7zfIGWBau42xe46gmqabwYkpPlCLIjcEY1XK+auP7iGRD9/205BPyoQdY8hT96MPpgERdC9Q==} deprecated: This package has been deprecated and now it only exports makeExecutableSchema.\nAnd it will no longer receive updates.\nWe recommend you to migrate to scoped packages such as @graphql-tools/schema, @graphql-tools/utils and etc.\nCheck out https://www.graphql-tools.com to learn what package you should use instead @@ -9127,6 +9256,11 @@ packages: dependencies: iterall: 1.3.0 + /graphql/15.8.0: + resolution: {integrity: sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw==} + engines: {node: '>= 10.x'} + dev: false + /graphql/16.6.0: resolution: {integrity: sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} From 867434caeb0fcb7add859f5a1aa2a21a32234200 Mon Sep 17 00:00:00 2001 From: Sujith Date: Thu, 3 Nov 2022 19:24:35 +0530 Subject: [PATCH 38/71] fix: pnpm-lock update without snyk Signed-off-by: Sujith --- pnpm-lock.yaml | 322 ++++++++++++------------------------------------- 1 file changed, 74 insertions(+), 248 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6d4218cb158..3cc712c27da 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -225,20 +225,20 @@ importers: '@reactioncommerce/api-plugin-payments-example': link:../../packages/api-plugin-payments-example '@reactioncommerce/api-plugin-payments-stripe-sca': link:../../packages/api-plugin-payments-stripe-sca '@reactioncommerce/api-plugin-pricing-simple': link:../../packages/api-plugin-pricing-simple - '@reactioncommerce/api-plugin-products': 1.3.1_graphql@14.7.0 + '@reactioncommerce/api-plugin-products': link:../../packages/api-plugin-products '@reactioncommerce/api-plugin-settings': link:../../packages/api-plugin-settings '@reactioncommerce/api-plugin-shipments': link:../../packages/api-plugin-shipments - '@reactioncommerce/api-plugin-shipments-flat-rate': 1.0.10_graphql@14.7.0 + '@reactioncommerce/api-plugin-shipments-flat-rate': link:../../packages/api-plugin-shipments-flat-rate '@reactioncommerce/api-plugin-shops': link:../../packages/api-plugin-shops '@reactioncommerce/api-plugin-simple-schema': link:../../packages/api-plugin-simple-schema '@reactioncommerce/api-plugin-sitemap-generator': link:../../packages/api-plugin-sitemap-generator - '@reactioncommerce/api-plugin-surcharges': 1.1.8_graphql@14.7.0 + '@reactioncommerce/api-plugin-surcharges': link:../../packages/api-plugin-surcharges '@reactioncommerce/api-plugin-system-information': link:../../packages/api-plugin-system-information '@reactioncommerce/api-plugin-tags': link:../../packages/api-plugin-tags '@reactioncommerce/api-plugin-taxes': link:../../packages/api-plugin-taxes '@reactioncommerce/api-plugin-taxes-flat-rate': link:../../packages/api-plugin-taxes-flat-rate '@reactioncommerce/api-plugin-translations': link:../../packages/api-plugin-translations - '@reactioncommerce/api-utils': 1.17.0_graphql@14.7.0 + '@reactioncommerce/api-utils': link:../../packages/api-utils '@reactioncommerce/db-version-check': link:../../packages/db-version-check '@reactioncommerce/file-collections': link:../../packages/file-collections '@reactioncommerce/file-collections-sa-gridfs': link:../../packages/file-collections-sa-gridfs @@ -297,7 +297,7 @@ importers: dependencies: '@apollo/federation': 0.15.1_graphql@14.7.0 '@graphql-tools/merge': 8.3.6_graphql@14.7.0 - '@reactioncommerce/api-utils': 1.17.0_graphql@14.7.0 + '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error @@ -349,7 +349,7 @@ importers: ramda: ^0.28.0 simpl-schema: ^1.8.0 dependencies: - '@reactioncommerce/api-utils': 1.17.0_graphql@14.7.0 + '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/db-version-check': link:../db-version-check '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random @@ -377,7 +377,7 @@ importers: lodash: ^4.17.15 simpl-schema: ^1.12.0 dependencies: - '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 + '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error lodash: 4.17.21 @@ -389,7 +389,7 @@ importers: '@reactioncommerce/babel-remove-es-create-require': ~1.0.0 faker: ^4.1.0 dependencies: - '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 + '@reactioncommerce/api-utils': link:../api-utils faker: 4.1.0 devDependencies: '@reactioncommerce/babel-remove-es-create-require': 1.0.0_@babel+core@7.19.0 @@ -413,18 +413,18 @@ importers: node-fetch: ^2.6.0 simpl-schema: ^1.5.7 dependencies: - '@accounts/graphql-api': 0.33.1_snjxfcqg4vfu4wt5s37iqh5esu + '@accounts/graphql-api': 0.33.1_2czzfrymqvhuggs422aynrtswq '@accounts/magic-link': 0.1.1_@accounts+server@0.33.1 '@accounts/mongo': 0.33.5 '@accounts/password': 0.32.2_@accounts+server@0.33.1 '@accounts/server': 0.33.1 - '@graphql-modules/core': 0.7.17_reflect-metadata@0.1.13 + '@graphql-modules/core': 0.7.17_47dpehqa77wbfro2ktaldhryyy '@reactioncommerce/api-core': link:../api-core - '@reactioncommerce/api-utils': 1.17.0 + '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/logger': link:../logger '@reactioncommerce/reaction-error': link:../reaction-error envalid: 6.0.2 - graphql-tag: 2.12.6 + graphql-tag: 2.12.6_graphql@14.7.0 jwt-decode: 3.1.2 mongoose: 6.6.0 node-fetch: 2.6.7 @@ -448,7 +448,7 @@ importers: lodash: ^4.17.15 ramda: ^0.28.0 dependencies: - '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 + '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/db-version-check': link:../db-version-check '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random @@ -486,7 +486,7 @@ importers: dependencies: '@reactioncommerce/api-plugin-address-validation': link:../api-plugin-address-validation '@reactioncommerce/api-plugin-tags': link:../api-plugin-tags - '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 + '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error @@ -520,7 +520,7 @@ importers: object-hash: ^2.0.3 simpl-schema: ^1.12.0 dependencies: - '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 + '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error @@ -543,7 +543,7 @@ importers: '@reactioncommerce/babel-remove-es-create-require': ~1.0.0 '@reactioncommerce/data-factory': ~1.0.1 dependencies: - '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 + '@reactioncommerce/api-utils': link:../api-utils devDependencies: '@reactioncommerce/babel-remove-es-create-require': 1.0.0_@babel+core@7.19.0 '@reactioncommerce/data-factory': 1.0.1 @@ -563,7 +563,7 @@ importers: babel-plugin-transform-import-meta: ~1.0.0 simpl-schema: ^1.12.0 dependencies: - '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 + '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error @@ -585,7 +585,7 @@ importers: '@reactioncommerce/db-version-check': ^1.0.0 '@reactioncommerce/logger': ^1.1.3 dependencies: - '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 + '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/db-version-check': link:../db-version-check '@reactioncommerce/logger': link:../logger devDependencies: @@ -608,7 +608,7 @@ importers: envalid: ^6.0.2 simpl-schema: ^1.12.0 dependencies: - '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 + '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/logger': link:../logger '@reactioncommerce/nodemailer': 5.0.5 '@reactioncommerce/reaction-error': link:../reaction-error @@ -634,7 +634,7 @@ importers: handlebars: ^4.7.6 simpl-schema: ^1.12.0 dependencies: - '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 + '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error @@ -659,7 +659,7 @@ importers: sharp: ^0.30.7 simpl-schema: ~1.10.2 dependencies: - '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 + '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/file-collections': link:../file-collections '@reactioncommerce/file-collections-sa-gridfs': link:../file-collections-sa-gridfs '@reactioncommerce/logger': link:../logger @@ -684,7 +684,7 @@ importers: lodash: ^4.17.21 simpl-schema: ^1.12.2 dependencies: - '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 + '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error @@ -701,7 +701,7 @@ importers: '@reactioncommerce/data-factory': ~1.0.1 lodash: ~4.17.15 dependencies: - '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 + '@reactioncommerce/api-utils': link:../api-utils lodash: 4.17.21 devDependencies: '@reactioncommerce/babel-remove-es-create-require': 1.0.0_@babel+core@7.19.0 @@ -721,7 +721,7 @@ importers: babel-plugin-transform-import-meta: ~1.0.0 lodash: ~4.17.21 dependencies: - '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 + '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/reaction-error': link:../reaction-error lodash: 4.17.21 devDependencies: @@ -750,7 +750,7 @@ importers: lodash: ^4.17.15 simpl-schema: ^1.12.0 dependencies: - '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 + '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error @@ -776,7 +776,7 @@ importers: later: ^1.2.0 simpl-schema: ^1.12.0 dependencies: - '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 + '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random envalid: 6.0.2 @@ -801,7 +801,7 @@ importers: lodash: ^4.17.15 simpl-schema: ^1.12.0 dependencies: - '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 + '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error lodash: 4.17.21 @@ -824,7 +824,7 @@ importers: '@reactioncommerce/random': ^1.0.2 simpl-schema: ^1.12.0 dependencies: - '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 + '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random simpl-schema: 1.12.3 @@ -851,7 +851,7 @@ importers: lodash: ^4.17.21 simpl-schema: ^1.5.9 dependencies: - '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 + '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error @@ -883,7 +883,7 @@ importers: babel-plugin-transform-import-meta: ~1.0.0 simpl-schema: ^1.12.0 dependencies: - '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 + '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/reaction-error': link:../reaction-error simpl-schema: 1.12.3 devDependencies: @@ -908,7 +908,7 @@ importers: babel-plugin-transform-es2015-modules-commonjs: ^6.26.2 babel-plugin-transform-import-meta: ~1.0.0 dependencies: - '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 + '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/random': link:../random devDependencies: '@babel/core': 7.19.0 @@ -938,7 +938,7 @@ importers: stripe: ^8.222.0 dependencies: '@reactioncommerce/api-plugin-carts': link:../api-plugin-carts - '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 + '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error @@ -970,7 +970,7 @@ importers: ramda: ^0.28.0 simpl-schema: ^1.12.0 dependencies: - '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 + '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/logger': link:../logger '@reactioncommerce/reaction-error': link:../reaction-error accounting-js: 1.1.1 @@ -1001,7 +1001,7 @@ importers: lodash: 4.17.21 simpl-schema: ^1.12.0 dependencies: - '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 + '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error @@ -1031,7 +1031,7 @@ importers: babel-plugin-transform-import-meta: ~1.0.0 envalid: ^7.3.1 dependencies: - '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 + '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/file-collections': link:../file-collections '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random @@ -1055,7 +1055,7 @@ importers: '@reactioncommerce/reaction-error': ~1.0.1 simpl-schema: ~1.12.0 dependencies: - '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 + '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/reaction-error': link:../reaction-error simpl-schema: 1.12.3 devDependencies: @@ -1080,7 +1080,7 @@ importers: lodash: ^4.17.21 simpl-schema: ^1.5.7 dependencies: - '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 + '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/logger': link:../logger '@reactioncommerce/reaction-error': link:../reaction-error lodash: 4.17.21 @@ -1111,7 +1111,7 @@ importers: lodash: ^4.17.15 simpl-schema: ^1.12.0 dependencies: - '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 + '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error @@ -1143,7 +1143,7 @@ importers: simpl-schema: ~1.12.0 dependencies: '@reactioncommerce/api-plugin-address-validation': link:../api-plugin-address-validation - '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 + '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error @@ -1163,7 +1163,7 @@ importers: '@reactioncommerce/babel-remove-es-create-require': ~1.0.0 '@reactioncommerce/data-factory': ~1.0.1 dependencies: - '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 + '@reactioncommerce/api-utils': link:../api-utils devDependencies: '@reactioncommerce/babel-remove-es-create-require': 1.0.0_@babel+core@7.19.0 '@reactioncommerce/data-factory': 1.0.1 @@ -1175,7 +1175,7 @@ importers: '@reactioncommerce/reaction-error': ^1.0.1 simpl-schema: ^1.12.0 dependencies: - '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 + '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/logger': link:../logger '@reactioncommerce/reaction-error': link:../reaction-error simpl-schema: 1.12.3 @@ -1194,7 +1194,7 @@ importers: babel-plugin-transform-import-meta: ~1.0.0 simpl-schema: ^1.12.0 dependencies: - '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 + '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error simpl-schema: 1.12.3 @@ -1220,7 +1220,7 @@ importers: babel-plugin-transform-es2015-modules-commonjs: ^6.26.2 babel-plugin-transform-import-meta: ~1.0.0 dependencies: - '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 + '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/logger': link:../logger '@reactioncommerce/reaction-error': link:../reaction-error devDependencies: @@ -1249,7 +1249,7 @@ importers: lodash: ~4.17.21 simpl-schema: ~1.10.2 dependencies: - '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 + '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/data-factory': 1.0.1 '@reactioncommerce/db-version-check': link:../db-version-check '@reactioncommerce/file-collections': link:../file-collections @@ -1283,7 +1283,7 @@ importers: lodash: ^4.17.15 simpl-schema: ^1.12.0 dependencies: - '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 + '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/logger': link:../logger '@reactioncommerce/reaction-error': link:../reaction-error accounting-js: 1.1.1 @@ -1309,7 +1309,7 @@ importers: '@reactioncommerce/reaction-error': ^1.0.1 simpl-schema: ^1.12.0 dependencies: - '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 + '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error simpl-schema: 1.12.3 @@ -1325,7 +1325,7 @@ importers: '@reactioncommerce/babel-remove-es-create-require': ~1.0.0 '@reactioncommerce/data-factory': ~1.0.1 dependencies: - '@reactioncommerce/api-utils': 1.17.0_graphql@15.8.0 + '@reactioncommerce/api-utils': link:../api-utils devDependencies: '@babel/core': 7.19.0 '@babel/preset-env': 7.19.0_@babel+core@7.19.0 @@ -1533,7 +1533,7 @@ importers: packages: - /@accounts/graphql-api/0.33.1_snjxfcqg4vfu4wt5s37iqh5esu: + /@accounts/graphql-api/0.33.1_2czzfrymqvhuggs422aynrtswq: resolution: {integrity: sha512-wvseq3lCOPTcfao4VYs0v+dABJV+j1XTOHs8LS37I6Uk7/6jMkBlPlWKptdREEOnPM+b5rK7YiGpSFAC2LalPg==} peerDependencies: '@accounts/magic-link': ^0.1.0 @@ -1548,10 +1548,11 @@ packages: '@accounts/password': 0.32.2_@accounts+server@0.33.1 '@accounts/server': 0.33.1 '@accounts/types': 0.33.2 - '@graphql-modules/core': 0.7.17_reflect-metadata@0.1.13 - '@graphql-tools/merge': 6.2.13 - '@graphql-tools/utils': 7.9.0 - graphql-tag: 2.12.6 + '@graphql-modules/core': 0.7.17_47dpehqa77wbfro2ktaldhryyy + '@graphql-tools/merge': 6.2.13_graphql@14.7.0 + '@graphql-tools/utils': 7.9.0_graphql@14.7.0 + graphql: 14.7.0 + graphql-tag: 2.12.6_graphql@14.7.0 request-ip: 2.1.3 tslib: 2.3.0 dev: false @@ -4002,17 +4003,18 @@ packages: - supports-color dev: false - /@graphql-modules/core/0.7.17_reflect-metadata@0.1.13: + /@graphql-modules/core/0.7.17_47dpehqa77wbfro2ktaldhryyy: resolution: {integrity: sha512-hGJa1VIsIHTKJ0Hc5gJfFrdhHAF1Vm+LWYeMNC5mPbVd/IZA1wVSpdLDjjliylLI6GnVRu1YreS2gXvFNXPJFA==} peerDependencies: graphql: ^14.1.1 || ^15.0.0 dependencies: '@graphql-modules/di': 0.7.17_reflect-metadata@0.1.13 - '@graphql-toolkit/common': 0.10.6 - '@graphql-toolkit/schema-merging': 0.10.6 + '@graphql-toolkit/common': 0.10.6_graphql@14.7.0 + '@graphql-toolkit/schema-merging': 0.10.6_graphql@14.7.0 apollo-server-caching: 0.5.1 deepmerge: 4.2.2 - graphql-tools: 4.0.5 + graphql: 14.7.0 + graphql-tools: 4.0.5_graphql@14.7.0 tslib: 2.0.0 transitivePeerDependencies: - reflect-metadata @@ -4028,7 +4030,7 @@ packages: tslib: 2.0.0 dev: false - /@graphql-toolkit/common/0.10.6: + /@graphql-toolkit/common/0.10.6_graphql@14.7.0: resolution: {integrity: sha512-rrH/KPheh/wCZzqUmNayBHd+aNWl/751C4iTL/327TzONdAVrV7ZQOyEkpGLW6YEFWPIlWxNkaBoEALIjCxTGg==} deprecated: GraphQL Toolkit is deprecated and merged into GraphQL Tools, so it will no longer get updates. Use GraphQL Tools instead to stay up-to-date! Check out https://www.graphql-tools.com/docs/migration-from-toolkit for migration and https://the-guild.dev/blog/graphql-tools-v6 for new changes. peerDependencies: @@ -4036,28 +4038,31 @@ packages: dependencies: aggregate-error: 3.0.1 camel-case: 4.1.1 - graphql-tools: 4.0.5 + graphql: 14.7.0 + graphql-tools: 4.0.5_graphql@14.7.0 lodash: 4.17.15 dev: false - /@graphql-toolkit/schema-merging/0.10.6: + /@graphql-toolkit/schema-merging/0.10.6_graphql@14.7.0: resolution: {integrity: sha512-BNABgYaNCw4Li3EiH/x7oDpkN+ml3M0SWqjnsW1Pf2NcyfGlv033Bda+O/q4XYtseZ0OOOh52GLXtUgwyPFb8A==} peerDependencies: graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 dependencies: - '@graphql-toolkit/common': 0.10.6 + '@graphql-toolkit/common': 0.10.6_graphql@14.7.0 deepmerge: 4.2.2 - graphql-tools: 4.0.5 + graphql: 14.7.0 + graphql-tools: 4.0.5_graphql@14.7.0 tslib: 1.11.1 dev: false - /@graphql-tools/merge/6.2.13: + /@graphql-tools/merge/6.2.13_graphql@14.7.0: resolution: {integrity: sha512-Qjlki0fp+bBQPinhdv7rv24eurvThZ5oIFvGMpLxMZplbw/ovJ2c6llwXr5PCuWAk9HGZsyM9NxxDgtTRfq3dQ==} peerDependencies: graphql: ^14.0.0 || ^15.0.0 dependencies: - '@graphql-tools/schema': 7.1.5 - '@graphql-tools/utils': 7.9.0 + '@graphql-tools/schema': 7.1.5_graphql@14.7.0 + '@graphql-tools/utils': 7.9.0_graphql@14.7.0 + graphql: 14.7.0 tslib: 2.2.0 dev: false @@ -4071,23 +4076,25 @@ packages: tslib: 2.4.0 dev: false - /@graphql-tools/schema/7.1.5: + /@graphql-tools/schema/7.1.5_graphql@14.7.0: resolution: {integrity: sha512-uyn3HSNSckf4mvQSq0Q07CPaVZMNFCYEVxroApOaw802m9DcZPgf9XVPy/gda5GWj9AhbijfRYVTZQgHnJ4CXA==} peerDependencies: graphql: ^14.0.0 || ^15.0.0 dependencies: - '@graphql-tools/utils': 7.9.0 + '@graphql-tools/utils': 7.9.0_graphql@14.7.0 + graphql: 14.7.0 tslib: 2.2.0 value-or-promise: 1.0.6 dev: false - /@graphql-tools/utils/7.9.0: + /@graphql-tools/utils/7.9.0_graphql@14.7.0: resolution: {integrity: sha512-WaYfdKmYFw7Rw5BmFehwo5zqoHNlO1soKVSdrh4qN0X1U34g4aqESAMYogtlp2yWDb2b3qKShiByCvRWNypbVA==} peerDependencies: graphql: ^14.0.0 || ^15.0.0 dependencies: '@ardatan/aggregate-error': 0.0.6 camel-case: 4.1.2 + graphql: 14.7.0 tslib: 2.2.0 dev: false @@ -4607,106 +4614,6 @@ packages: /@protobufjs/utf8/1.1.0: resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} - /@reactioncommerce/api-plugin-products/1.3.1_graphql@14.7.0: - resolution: {integrity: sha512-lnjj9QkRlibsBUOZAgGNi9S13dbZogTouaZ59/+fiIlYJDrSOJ1x4yylGdWMZ6W7K2PvWoZvXk1YrzGopnCflQ==} - engines: {node: '>=14.18.1'} - dependencies: - '@reactioncommerce/api-utils': 1.17.0_graphql@14.7.0 - '@reactioncommerce/logger': 1.1.5 - '@reactioncommerce/random': 1.0.2 - '@reactioncommerce/reaction-error': 1.0.1 - lodash: 4.17.21 - simpl-schema: 1.12.3 - transitivePeerDependencies: - - graphql - dev: false - - /@reactioncommerce/api-plugin-shipments-flat-rate/1.0.10_graphql@14.7.0: - resolution: {integrity: sha512-XilxXESaBQa6v1ChSTduYg8kCTYElI9jy6lWevrfjkRjquSlA/sNZmZOMSkRW0tdZhZMCnAboH+C2r/62IDHBg==} - engines: {node: '>=14.18.1'} - dependencies: - '@reactioncommerce/api-utils': 1.17.0_graphql@14.7.0 - '@reactioncommerce/logger': 1.1.5 - '@reactioncommerce/random': 1.0.2 - '@reactioncommerce/reaction-error': 1.0.1 - lodash: 4.17.21 - simpl-schema: 1.12.3 - transitivePeerDependencies: - - graphql - dev: false - - /@reactioncommerce/api-plugin-surcharges/1.1.8_graphql@14.7.0: - resolution: {integrity: sha512-rPuZP4OswJlN28AU/TctLMVoNzLnwQZJWSLVXvJpwMZD9QOxHc40IjSazP5sMd52sMX/h4+PtYjMluIiNmGJ3g==} - engines: {node: '>=14.18.1'} - dependencies: - '@reactioncommerce/api-utils': 1.17.0_graphql@14.7.0 - '@reactioncommerce/random': 1.0.2 - '@reactioncommerce/reaction-error': 1.0.1 - simpl-schema: 1.12.3 - transitivePeerDependencies: - - graphql - dev: false - - /@reactioncommerce/api-utils/1.17.0: - resolution: {integrity: sha512-AJ78ytkirkbwPqp/WyctWAkk23Y7byenUT7kZ0uAuDIDcDPWHc0Qv6NgwNF3eaHpoZT08IwkycAp+0zFnPt0TA==} - engines: {node: '>=14.18.1'} - dependencies: - '@jest/globals': 26.6.2 - '@reactioncommerce/logger': 1.1.5 - '@reactioncommerce/random': 1.0.2 - '@reactioncommerce/reaction-error': 1.0.1 - accounting-js: 1.1.1 - callsite: 1.0.0 - envalid: 6.0.2 - graphql-fields: 2.0.3 - graphql-relay: 0.9.0 - lodash: 4.17.21 - ramda: 0.28.0 - transliteration: 2.3.5 - transitivePeerDependencies: - - graphql - dev: false - - /@reactioncommerce/api-utils/1.17.0_graphql@14.7.0: - resolution: {integrity: sha512-AJ78ytkirkbwPqp/WyctWAkk23Y7byenUT7kZ0uAuDIDcDPWHc0Qv6NgwNF3eaHpoZT08IwkycAp+0zFnPt0TA==} - engines: {node: '>=14.18.1'} - dependencies: - '@jest/globals': 26.6.2 - '@reactioncommerce/logger': 1.1.5 - '@reactioncommerce/random': 1.0.2 - '@reactioncommerce/reaction-error': 1.0.1 - accounting-js: 1.1.1 - callsite: 1.0.0 - envalid: 6.0.2 - graphql-fields: 2.0.3 - graphql-relay: 0.9.0_graphql@14.7.0 - lodash: 4.17.21 - ramda: 0.28.0 - transliteration: 2.3.5 - transitivePeerDependencies: - - graphql - dev: false - - /@reactioncommerce/api-utils/1.17.0_graphql@15.8.0: - resolution: {integrity: sha512-AJ78ytkirkbwPqp/WyctWAkk23Y7byenUT7kZ0uAuDIDcDPWHc0Qv6NgwNF3eaHpoZT08IwkycAp+0zFnPt0TA==} - engines: {node: '>=14.18.1'} - dependencies: - '@jest/globals': 26.6.2 - '@reactioncommerce/logger': 1.1.5 - '@reactioncommerce/random': 1.0.2 - '@reactioncommerce/reaction-error': 1.0.1 - accounting-js: 1.1.1 - callsite: 1.0.0 - envalid: 6.0.2 - graphql-fields: 2.0.3 - graphql-relay: 0.9.0_graphql@15.8.0 - lodash: 4.17.21 - ramda: 0.28.0 - transliteration: 2.3.5 - transitivePeerDependencies: - - graphql - dev: false - /@reactioncommerce/babel-remove-es-create-require/1.0.0_@babel+core@7.19.0: resolution: {integrity: sha512-yR1vMj76hK5D9/VcXjFpk2OMoYpQdbvEgGcJ79cEzTL/jkFwIs53Zl6PNBjcC608P8qHl5eypEYRwG7CCGLzwQ==} peerDependencies: @@ -4762,27 +4669,11 @@ packages: eslint-plugin-you-dont-need-lodash-underscore: 6.12.0 dev: true - /@reactioncommerce/logger/1.1.5: - resolution: {integrity: sha512-fG6wohv9NoX6ZjNzs0YcFRFxHyndO4VitMCrbz+8HrnM7LNJtQRzrUcR58RptagYixZ4GyiHY6WhmIfrMpNvLw==} - dependencies: - bunyan: 1.8.15 - bunyan-format: 0.2.1 - node-loggly-bulk: 2.2.5 - dev: false - /@reactioncommerce/nodemailer/5.0.5: resolution: {integrity: sha512-u4ontTETlROmLglkMDyouMXlX62NXOGfOUAd75Ilk3W4tcsRjRXX+g5C5B4mBCCcJB0wHn1yh/a4pOYkn81vUQ==} engines: {node: '>=4.0.0'} dev: false - /@reactioncommerce/random/1.0.2: - resolution: {integrity: sha512-02eARZh6FQ45tEXXZUwnz90XWQasLpnV140CcuOl/L3dhEL4Emu2oCZemVhKvNb2JMmm5vOG2Y0CUb12xAx73w==} - dev: false - - /@reactioncommerce/reaction-error/1.0.1: - resolution: {integrity: sha512-l1HyDTFw4m1j8EPWNA/bbF2GZZaLYQjwmgniSjjM1cgnhfjCqeLywi501oPkmH8E84Jh0EH+4ADhNCcZ4m4gug==} - dev: false - /@sinclair/typebox/0.24.41: resolution: {integrity: sha512-TJCgQurls4FipFvHeC+gfAzb+GGstL0TDwYJKQVtTeSvJIznWzP7g3bAd5gEBlr8+bIxqnWS9VGVWREDhmE8jA==} dev: true @@ -5447,17 +5338,6 @@ packages: tslib: 1.14.1 dev: true - /apollo-link/1.2.14: - resolution: {integrity: sha512-p67CMEFP7kOG1JZ0ZkYZwRDa369w5PIjtMjvrQd/HnIV8FRsHRqLqK+oAZQnFa1DDdZtOtHTi+aMIW6EatC2jg==} - peerDependencies: - graphql: ^0.11.3 || ^0.12.3 || ^0.13.0 || ^14.0.0 || ^15.0.0 - dependencies: - apollo-utilities: 1.3.4 - ts-invariant: 0.4.4 - tslib: 1.14.1 - zen-observable-ts: 0.8.21 - dev: false - /apollo-link/1.2.14_graphql@14.7.0: resolution: {integrity: sha512-p67CMEFP7kOG1JZ0ZkYZwRDa369w5PIjtMjvrQd/HnIV8FRsHRqLqK+oAZQnFa1DDdZtOtHTi+aMIW6EatC2jg==} peerDependencies: @@ -5666,17 +5546,6 @@ packages: transitivePeerDependencies: - encoding - /apollo-utilities/1.3.4: - resolution: {integrity: sha512-pk2hiWrCXMAy2fRPwEyhvka+mqwzeP60Jr1tRYi5xru+3ko94HI9o6lK0CT33/w4RDlxWchmdhDCrvdr+pHCig==} - peerDependencies: - graphql: ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 - dependencies: - '@wry/equality': 0.1.11 - fast-json-stable-stringify: 2.1.0 - ts-invariant: 0.4.4 - tslib: 1.14.1 - dev: false - /apollo-utilities/1.3.4_graphql@14.7.0: resolution: {integrity: sha512-pk2hiWrCXMAy2fRPwEyhvka+mqwzeP60Jr1tRYi5xru+3ko94HI9o6lK0CT33/w4RDlxWchmdhDCrvdr+pHCig==} peerDependencies: @@ -9158,13 +9027,6 @@ packages: graphql: 14.7.0 dev: false - /graphql-relay/0.9.0: - resolution: {integrity: sha512-yNJLCqcjz0XpzpmmckRJCSK8a2ZLwTurwrQ09UyGftONh52PbrGpK1UO4yspvj0c7pC+jkN4ZUqVXG3LRrWkXQ==} - engines: {node: ^12.20.0 || ^14.15.0 || >= 15.9.0} - peerDependencies: - graphql: ^15.5.3 - dev: false - /graphql-relay/0.9.0_graphql@14.7.0: resolution: {integrity: sha512-yNJLCqcjz0XpzpmmckRJCSK8a2ZLwTurwrQ09UyGftONh52PbrGpK1UO4yspvj0c7pC+jkN4ZUqVXG3LRrWkXQ==} engines: {node: ^12.20.0 || ^14.15.0 || >= 15.9.0} @@ -9174,15 +9036,6 @@ packages: graphql: 14.7.0 dev: false - /graphql-relay/0.9.0_graphql@15.8.0: - resolution: {integrity: sha512-yNJLCqcjz0XpzpmmckRJCSK8a2ZLwTurwrQ09UyGftONh52PbrGpK1UO4yspvj0c7pC+jkN4ZUqVXG3LRrWkXQ==} - engines: {node: ^12.20.0 || ^14.15.0 || >= 15.9.0} - peerDependencies: - graphql: ^15.5.3 - dependencies: - graphql: 15.8.0 - dev: false - /graphql-schema-linter/3.0.0_graphql@16.6.0: resolution: {integrity: sha512-nXsyFvcqrD9/QWgimvJjvRBsUXHw8O7XoPLhpnGZd9eX6r2ZXsOBCGhTzF9ZrFtWosCBEfBGU2xpFUPe0qCHVg==} hasBin: true @@ -9206,15 +9059,6 @@ packages: iterall: 1.3.0 dev: false - /graphql-tag/2.12.6: - resolution: {integrity: sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==} - engines: {node: '>=10'} - peerDependencies: - graphql: ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 - dependencies: - tslib: 2.4.0 - dev: false - /graphql-tag/2.12.6_graphql@14.7.0: resolution: {integrity: sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==} engines: {node: '>=10'} @@ -9224,19 +9068,6 @@ packages: graphql: 14.7.0 tslib: 2.4.0 - /graphql-tools/4.0.5: - resolution: {integrity: sha512-kQCh3IZsMqquDx7zfIGWBau42xe46gmqabwYkpPlCLIjcEY1XK+auP7iGRD9/205BPyoQdY8hT96MPpgERdC9Q==} - deprecated: This package has been deprecated and now it only exports makeExecutableSchema.\nAnd it will no longer receive updates.\nWe recommend you to migrate to scoped packages such as @graphql-tools/schema, @graphql-tools/utils and etc.\nCheck out https://www.graphql-tools.com to learn what package you should use instead - peerDependencies: - graphql: ^0.13.0 || ^14.0.0 - dependencies: - apollo-link: 1.2.14 - apollo-utilities: 1.3.4 - deprecated-decorator: 0.1.6 - iterall: 1.3.0 - uuid: 3.4.0 - dev: false - /graphql-tools/4.0.5_graphql@14.7.0: resolution: {integrity: sha512-kQCh3IZsMqquDx7zfIGWBau42xe46gmqabwYkpPlCLIjcEY1XK+auP7iGRD9/205BPyoQdY8hT96MPpgERdC9Q==} deprecated: This package has been deprecated and now it only exports makeExecutableSchema.\nAnd it will no longer receive updates.\nWe recommend you to migrate to scoped packages such as @graphql-tools/schema, @graphql-tools/utils and etc.\nCheck out https://www.graphql-tools.com to learn what package you should use instead @@ -9256,11 +9087,6 @@ packages: dependencies: iterall: 1.3.0 - /graphql/15.8.0: - resolution: {integrity: sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw==} - engines: {node: '>= 10.x'} - dev: false - /graphql/16.6.0: resolution: {integrity: sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} From 79eac5e87c24b620044d1dabebea1c7cdd30aecf Mon Sep 17 00:00:00 2001 From: Sujith Date: Fri, 4 Nov 2022 13:51:06 +0530 Subject: [PATCH 39/71] feat: data migration for fulfillment feature Signed-off-by: Sujith --- packages/api-plugin-fulfillment/index.js | 2 + .../api-plugin-fulfillment/migrations/2.js | 45 +++++++++++++++ .../api-plugin-fulfillment/migrations/3.js | 56 +++++++++++++++++++ .../api-plugin-fulfillment/migrations/4.js | 43 ++++++++++++++ .../migrations/index.js | 17 ++++++ .../migrations/migrationsNamespace.js | 1 + packages/api-plugin-fulfillment/package.json | 1 + .../api-plugin-fulfillment/src/preStartup.js | 33 +++++++++++ 8 files changed, 198 insertions(+) create mode 100644 packages/api-plugin-fulfillment/migrations/2.js create mode 100644 packages/api-plugin-fulfillment/migrations/3.js create mode 100644 packages/api-plugin-fulfillment/migrations/4.js create mode 100644 packages/api-plugin-fulfillment/migrations/index.js create mode 100644 packages/api-plugin-fulfillment/migrations/migrationsNamespace.js diff --git a/packages/api-plugin-fulfillment/index.js b/packages/api-plugin-fulfillment/index.js index d7ea8b28c59..ff1789c8e87 100644 --- a/packages/api-plugin-fulfillment/index.js +++ b/packages/api-plugin-fulfillment/index.js @@ -1,3 +1,5 @@ import register from "./src/index.js"; +export { default as migrations } from "./migrations/index.js"; + export default register; diff --git a/packages/api-plugin-fulfillment/migrations/2.js b/packages/api-plugin-fulfillment/migrations/2.js new file mode 100644 index 00000000000..a1259d1ce57 --- /dev/null +++ b/packages/api-plugin-fulfillment/migrations/2.js @@ -0,0 +1,45 @@ +const COLL_FF_SOURCE = "Shipments"; +const COLL_FF_DEST = "Fulfillment"; +const COLL_FFR_SOURCE = "FlatRateFulfillmentRestrictions"; +const COLL_FFR_DEST = "FulfillmentRestrictions"; + +/** + * @summary migrates the database down one version + * @param {Object} context Migration context + * @param {Object} context.db MongoDB `Db` instance + * @param {Function} context.progress A function to report progress, takes percent + * number as argument. + * @return {undefined} + */ +async function down({ db, progress }) { + progress(0); + + await db.collections(COLL_FF_DEST).drop(); + progress(50); + + await db.collections(COLL_FFR_DEST).drop(); + progress(100); +} + +/** + * @summary Performs migration up from previous data version + * @param {Object} context Migration context + * @param {Object} context.db MongoDB `Db` instance + * @param {Function} context.progress A function to report progress, takes percent + * number as argument. + * @return {undefined} + */ +async function up({ db, progress }) { + progress(0); + + await db.collections(COLL_FF_SOURCE).aggregate([{ $match: {} }, { $out: COLL_FF_DEST }]); + progress(50); + + await db.collections(COLL_FFR_SOURCE).aggregate([{ $match: {} }, { $out: COLL_FFR_DEST }]); + progress(100); +} + +export default { + down, + up +}; diff --git a/packages/api-plugin-fulfillment/migrations/3.js b/packages/api-plugin-fulfillment/migrations/3.js new file mode 100644 index 00000000000..1becbedaf41 --- /dev/null +++ b/packages/api-plugin-fulfillment/migrations/3.js @@ -0,0 +1,56 @@ +const FF_TYPE = "shipping"; +const FF_METHOD = "flatRate"; +const COLL_DEST = "Fulfillment"; + +/** + * @summary Performs migration up from previous data version + * @param {Object} context Migration context + * @param {Object} context.db MongoDB `Db` instance + * @param {Function} context.progress A function to report progress, takes percent + * number as argument. + * @return {undefined} + */ +async function up({ db, progress }) { + progress(0); + + const operations = []; + + const ffTypeUpdate = { + updateMany: { + filter: { fulfillmentType: { $exists: false } }, + update: { + $set: { + fulfillmentType: FF_TYPE + } + } + } + }; + + const ffMethodUpdate = { + updateMany: { + filter: { methods: { $exists: true } }, + update: { + $set: { + "methods.$[eachMethod].fulfillmentMethod": FF_METHOD + } + }, + arrayFilters: [ + { + "eachMethod.fulfillmentMethod": { $exists: false } + } + ] + } + }; + + operations.push(ffTypeUpdate); + operations.push(ffMethodUpdate); + + await db.collections(COLL_DEST).bulkWrite(operations); + + progress(100); +} + +export default { + down: "impossible", // We are not tracking the updated documents, hence cannot revert + up +}; diff --git a/packages/api-plugin-fulfillment/migrations/4.js b/packages/api-plugin-fulfillment/migrations/4.js new file mode 100644 index 00000000000..48534ccd3c2 --- /dev/null +++ b/packages/api-plugin-fulfillment/migrations/4.js @@ -0,0 +1,43 @@ +/** + * @summary Performs migration up from previous data version + * @param {Object} context Migration context + * @param {Object} context.db MongoDB `Db` instance + * @param {Function} context.progress A function to report progress, takes percent + * number as argument. + * @return {undefined} + */ +async function up({ db, progress }) { + progress(0); + const affectedGroups = [ + "owner", + "shop manager" + ]; + + const newShopPermissions = [ + "reaction:legacy:fulfillmentRestrictions/create", + "reaction:legacy:fulfillmentRestrictions/delete", + "reaction:legacy:fulfillmentRestrictions/read", + "reaction:legacy:fulfillmentRestrictions/update", + "reaction:legacy:fulfillmentTypes/create", + "reaction:legacy:fulfillmentTypes/delete", + "reaction:legacy:fulfillmentTypes/read", + "reaction:legacy:fulfillmentTypes/update", + "reaction:legacy:fulfillmentMethods/create", + "reaction:legacy:fulfillmentMethods/delete", + "reaction:legacy:fulfillmentMethods/read", + "reaction:legacy:fulfillmentMethods/update" + ]; + + await db.collection("Groups").updateMany({ + slug: { $in: affectedGroups } + }, { + $addToSet: { permissions: { $each: newShopPermissions } } + }); + + progress(100); +} + +export default { + down: "impossible", + up +}; diff --git a/packages/api-plugin-fulfillment/migrations/index.js b/packages/api-plugin-fulfillment/migrations/index.js new file mode 100644 index 00000000000..783e2bf9115 --- /dev/null +++ b/packages/api-plugin-fulfillment/migrations/index.js @@ -0,0 +1,17 @@ +import { migrationsNamespace } from "./migrationsNamespace.js"; +import migration2 from "./2.js"; +import migration3 from "./3.js"; +import migration4 from "./4.js"; + +export default { + tracks: [ + { + namespace: migrationsNamespace, + migrations: { + 2: migration2, + 3: migration3, + 4: migration4 + } + } + ] +}; diff --git a/packages/api-plugin-fulfillment/migrations/migrationsNamespace.js b/packages/api-plugin-fulfillment/migrations/migrationsNamespace.js new file mode 100644 index 00000000000..49c2b68e1ce --- /dev/null +++ b/packages/api-plugin-fulfillment/migrations/migrationsNamespace.js @@ -0,0 +1 @@ +export const migrationsNamespace = "fulfillment"; diff --git a/packages/api-plugin-fulfillment/package.json b/packages/api-plugin-fulfillment/package.json index 5497612a419..342f5c67bf2 100644 --- a/packages/api-plugin-fulfillment/package.json +++ b/packages/api-plugin-fulfillment/package.json @@ -32,6 +32,7 @@ "@reactioncommerce/api-utils": "^1.16.9", "@reactioncommerce/reaction-error": "^1.0.1", "@reactioncommerce/random": "^1.0.2", + "@reactioncommerce/db-version-check": "^1.0.0", "lodash": "^4.17.21", "simpl-schema": "^1.12.2" }, diff --git a/packages/api-plugin-fulfillment/src/preStartup.js b/packages/api-plugin-fulfillment/src/preStartup.js index e65d3743063..6db7d24c595 100644 --- a/packages/api-plugin-fulfillment/src/preStartup.js +++ b/packages/api-plugin-fulfillment/src/preStartup.js @@ -1,8 +1,40 @@ import Logger from "@reactioncommerce/logger"; import ReactionError from "@reactioncommerce/reaction-error"; +import doesDatabaseVersionMatch from "@reactioncommerce/db-version-check"; +import { migrationsNamespace } from "../migrations/migrationsNamespace.js"; import { extendFulfillmentSchemas } from "./simpleSchemas.js"; const logCtx = { name: "fulfillment", file: "preStartup" }; +const expectedVersion = 4; + +/** + * @summary Checks if the version of the database matches requirement + * @param {Object} context Startup context + * @returns {undefined} + */ +async function dbVersionCheck(context) { + const setToExpectedIfMissing = async () => { + const anyFulfillment = await context.collections.Fulfillment.findOne(); + const anyRestriction = await context.collections.FulfillmentRestrictions.findOne(); + + return !anyFulfillment || !anyRestriction; + }; + + const ok = await doesDatabaseVersionMatch({ + // `db` is a Db instance from the `mongodb` NPM package, + // such as what is returned when you do `client.db()` + db: context.app.db, + // These must match one of the namespaces and versions + // your package exports in the `migrations` named export + expectedVersion, + namespace: migrationsNamespace, + setToExpectedIfMissing + }); + + if (!ok) { + throw new Error(`Database needs migrating. The "${migrationsNamespace}" namespace must be at version ${expectedVersion}.`); + } +} /** * @summary Called before startup to extend schemas @@ -10,6 +42,7 @@ const logCtx = { name: "fulfillment", file: "preStartup" }; * @returns {undefined} */ export default async function fulfillmentPreStartup(context) { + await dbVersionCheck(context); const allFulfillmentTypesArray = context.fulfillment?.registeredFulfillmentTypes; if (!allFulfillmentTypesArray || allFulfillmentTypesArray.length === 0) { From c5ab64e757b4cb461953a21eec97181fa8069843 Mon Sep 17 00:00:00 2001 From: Sujith Date: Tue, 4 Oct 2022 16:48:07 +0530 Subject: [PATCH 40/71] feat: migrate shopsetting for default ff-type Signed-off-by: Sujith --- packages/api-plugin-fulfillment/migrations/4.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/api-plugin-fulfillment/migrations/4.js b/packages/api-plugin-fulfillment/migrations/4.js index 48534ccd3c2..9a589926ad9 100644 --- a/packages/api-plugin-fulfillment/migrations/4.js +++ b/packages/api-plugin-fulfillment/migrations/4.js @@ -14,6 +14,7 @@ async function up({ db, progress }) { ]; const newShopPermissions = [ + "reaction:legacy:fulfillmentTypes/update:settings", "reaction:legacy:fulfillmentRestrictions/create", "reaction:legacy:fulfillmentRestrictions/delete", "reaction:legacy:fulfillmentRestrictions/read", From a6356cbbd3042eaf5825f65b20e9dd1b6996c8fd Mon Sep 17 00:00:00 2001 From: Sujith Date: Thu, 6 Oct 2022 17:39:01 +0530 Subject: [PATCH 41/71] fix: migration script fix Signed-off-by: Sujith --- packages/api-plugin-fulfillment/migrations/2.js | 8 +++++--- packages/api-plugin-fulfillment/migrations/3.js | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/api-plugin-fulfillment/migrations/2.js b/packages/api-plugin-fulfillment/migrations/2.js index a1259d1ce57..0ea010f9449 100644 --- a/packages/api-plugin-fulfillment/migrations/2.js +++ b/packages/api-plugin-fulfillment/migrations/2.js @@ -1,4 +1,4 @@ -const COLL_FF_SOURCE = "Shipments"; +const COLL_FF_SOURCE = "Shipping"; const COLL_FF_DEST = "Fulfillment"; const COLL_FFR_SOURCE = "FlatRateFulfillmentRestrictions"; const COLL_FFR_DEST = "FulfillmentRestrictions"; @@ -32,10 +32,12 @@ async function down({ db, progress }) { async function up({ db, progress }) { progress(0); - await db.collections(COLL_FF_SOURCE).aggregate([{ $match: {} }, { $out: COLL_FF_DEST }]); + const shippingCopyResp = await db.collection(COLL_FF_SOURCE).aggregate([{ $match: {} }, { $out: COLL_FF_DEST }]).next(); + if (shippingCopyResp) throw new Error("Error in copying Shipping collection"); // above command returns null if successful progress(50); - await db.collections(COLL_FFR_SOURCE).aggregate([{ $match: {} }, { $out: COLL_FFR_DEST }]); + const flatRateRestCopyResp = await db.collection(COLL_FFR_SOURCE).aggregate([{ $match: {} }, { $out: COLL_FFR_DEST }]).next(); + if (flatRateRestCopyResp) throw new Error("Error in copying FlatRateFulfillmentRestrictions collection"); // above command returns null if successful progress(100); } diff --git a/packages/api-plugin-fulfillment/migrations/3.js b/packages/api-plugin-fulfillment/migrations/3.js index 1becbedaf41..770899cc5dc 100644 --- a/packages/api-plugin-fulfillment/migrations/3.js +++ b/packages/api-plugin-fulfillment/migrations/3.js @@ -45,8 +45,8 @@ async function up({ db, progress }) { operations.push(ffTypeUpdate); operations.push(ffMethodUpdate); - await db.collections(COLL_DEST).bulkWrite(operations); - + const bulkWriteResp = await db.collection(COLL_DEST).bulkWrite(operations); + if (bulkWriteResp.writeErrors && bulkWriteResp.writeErrors.length) throw new Error("Error while updating Fulfillment collection"); progress(100); } From 3d016d77d4cf0f7e8966190e58bc8d3923fc9a3c Mon Sep 17 00:00:00 2001 From: Sujith Date: Thu, 13 Oct 2022 11:08:19 +0530 Subject: [PATCH 42/71] fix: review comment fixes Signed-off-by: Sujith --- .../api-plugin-fulfillment/migrations/3.js | 2 +- .../api-plugin-fulfillment/migrations/4.js | 66 ++++++++++++------- 2 files changed, 44 insertions(+), 24 deletions(-) diff --git a/packages/api-plugin-fulfillment/migrations/3.js b/packages/api-plugin-fulfillment/migrations/3.js index 770899cc5dc..e1b1506034e 100644 --- a/packages/api-plugin-fulfillment/migrations/3.js +++ b/packages/api-plugin-fulfillment/migrations/3.js @@ -51,6 +51,6 @@ async function up({ db, progress }) { } export default { - down: "impossible", // We are not tracking the updated documents, hence cannot revert + down: "unnecessary", // The newly created collections would get dropped in stage2 up }; diff --git a/packages/api-plugin-fulfillment/migrations/4.js b/packages/api-plugin-fulfillment/migrations/4.js index 9a589926ad9..af2dda9bbdf 100644 --- a/packages/api-plugin-fulfillment/migrations/4.js +++ b/packages/api-plugin-fulfillment/migrations/4.js @@ -1,3 +1,43 @@ +const affectedGlobalGroups = [ + "owner", + "shop manager" +]; + +const newGlobalPermissions = [ + "reaction:legacy:fulfillmentRestrictions/create", + "reaction:legacy:fulfillmentRestrictions/delete", + "reaction:legacy:fulfillmentRestrictions/read", + "reaction:legacy:fulfillmentRestrictions/update", + "reaction:legacy:fulfillmentTypes/create", + "reaction:legacy:fulfillmentTypes/delete", + "reaction:legacy:fulfillmentTypes/read", + "reaction:legacy:fulfillmentTypes/update", + "reaction:legacy:fulfillmentMethods/create", + "reaction:legacy:fulfillmentMethods/delete", + "reaction:legacy:fulfillmentMethods/read", + "reaction:legacy:fulfillmentMethods/update" +]; + +/** + * @summary migrates the database down one version + * @param {Object} context Migration context + * @param {Object} context.db MongoDB `Db` instance + * @param {Function} context.progress A function to report progress, takes percent + * number as argument. + * @return {undefined} + */ +async function down({ db, progress }) { + progress(0); + + await db.collection("Groups").updateMany({ + slug: { $in: affectedGlobalGroups } + }, { + $pullAll: { permissions: newGlobalPermissions } + }); + + progress(100); +} + /** * @summary Performs migration up from previous data version * @param {Object} context Migration context @@ -8,37 +48,17 @@ */ async function up({ db, progress }) { progress(0); - const affectedGroups = [ - "owner", - "shop manager" - ]; - - const newShopPermissions = [ - "reaction:legacy:fulfillmentTypes/update:settings", - "reaction:legacy:fulfillmentRestrictions/create", - "reaction:legacy:fulfillmentRestrictions/delete", - "reaction:legacy:fulfillmentRestrictions/read", - "reaction:legacy:fulfillmentRestrictions/update", - "reaction:legacy:fulfillmentTypes/create", - "reaction:legacy:fulfillmentTypes/delete", - "reaction:legacy:fulfillmentTypes/read", - "reaction:legacy:fulfillmentTypes/update", - "reaction:legacy:fulfillmentMethods/create", - "reaction:legacy:fulfillmentMethods/delete", - "reaction:legacy:fulfillmentMethods/read", - "reaction:legacy:fulfillmentMethods/update" - ]; await db.collection("Groups").updateMany({ - slug: { $in: affectedGroups } + slug: { $in: affectedGlobalGroups } }, { - $addToSet: { permissions: { $each: newShopPermissions } } + $addToSet: { permissions: { $each: newGlobalPermissions } } }); progress(100); } export default { - down: "impossible", + down, up }; From 3acaeb45017ca5fb11b865d312da4333f892a451 Mon Sep 17 00:00:00 2001 From: Sujith Date: Thu, 13 Oct 2022 18:17:44 +0530 Subject: [PATCH 43/71] fix: typo in 2.js Signed-off-by: Sujith --- packages/api-plugin-fulfillment/migrations/2.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/api-plugin-fulfillment/migrations/2.js b/packages/api-plugin-fulfillment/migrations/2.js index 0ea010f9449..17160fc81fc 100644 --- a/packages/api-plugin-fulfillment/migrations/2.js +++ b/packages/api-plugin-fulfillment/migrations/2.js @@ -14,10 +14,10 @@ const COLL_FFR_DEST = "FulfillmentRestrictions"; async function down({ db, progress }) { progress(0); - await db.collections(COLL_FF_DEST).drop(); + await db.collection(COLL_FF_DEST).drop(); progress(50); - await db.collections(COLL_FFR_DEST).drop(); + await db.collection(COLL_FFR_DEST).drop(); progress(100); } From bc2974d5c40d9560e2dfd81fdfdf83c72ddf7ad8 Mon Sep 17 00:00:00 2001 From: Sujith Date: Mon, 7 Nov 2022 18:57:26 +0530 Subject: [PATCH 44/71] fix: review comment fixes2 Signed-off-by: Sujith --- .../api-plugin-fulfillment/migrations/2.js | 132 +++++++++++++++++- .../api-plugin-fulfillment/migrations/3.js | 56 -------- .../api-plugin-fulfillment/migrations/4.js | 64 --------- .../migrations/index.js | 6 +- 4 files changed, 132 insertions(+), 126 deletions(-) delete mode 100644 packages/api-plugin-fulfillment/migrations/3.js delete mode 100644 packages/api-plugin-fulfillment/migrations/4.js diff --git a/packages/api-plugin-fulfillment/migrations/2.js b/packages/api-plugin-fulfillment/migrations/2.js index 17160fc81fc..a08b09959c0 100644 --- a/packages/api-plugin-fulfillment/migrations/2.js +++ b/packages/api-plugin-fulfillment/migrations/2.js @@ -2,6 +2,25 @@ const COLL_FF_SOURCE = "Shipping"; const COLL_FF_DEST = "Fulfillment"; const COLL_FFR_SOURCE = "FlatRateFulfillmentRestrictions"; const COLL_FFR_DEST = "FulfillmentRestrictions"; +const FF_TYPE = "shipping"; +const FF_METHOD = "flatRate"; +const COLL_DEST = "Fulfillment"; + +const newGlobalPermissions = [ + "reaction:legacy:fulfillmentTypes/update:settings", + "reaction:legacy:fulfillmentRestrictions/create", + "reaction:legacy:fulfillmentRestrictions/delete", + "reaction:legacy:fulfillmentRestrictions/read", + "reaction:legacy:fulfillmentRestrictions/update", + "reaction:legacy:fulfillmentTypes/create", + "reaction:legacy:fulfillmentTypes/delete", + "reaction:legacy:fulfillmentTypes/read", + "reaction:legacy:fulfillmentTypes/update", + "reaction:legacy:fulfillmentMethods/create", + "reaction:legacy:fulfillmentMethods/delete", + "reaction:legacy:fulfillmentMethods/read", + "reaction:legacy:fulfillmentMethods/update" +]; /** * @summary migrates the database down one version @@ -14,6 +33,23 @@ const COLL_FFR_DEST = "FulfillmentRestrictions"; async function down({ db, progress }) { progress(0); + const allGroups = await db.collection("Groups").find({}).toArray(); + const affectedGlobalGroups = []; + allGroups.forEach((group) => { + const perms = group.permissions; + if (perms && Array.isArray(perms) && perms.length) { + const found = newGlobalPermissions.some((elem) => perms.includes(elem)); + if (found) affectedGlobalGroups.push(group.slug); + } + }); + + await db.collection("Groups").updateMany({ + slug: { $in: affectedGlobalGroups } + }, { + $pullAll: { permissions: newGlobalPermissions } + }); + progress(10); + await db.collection(COLL_FF_DEST).drop(); progress(50); @@ -34,10 +70,104 @@ async function up({ db, progress }) { const shippingCopyResp = await db.collection(COLL_FF_SOURCE).aggregate([{ $match: {} }, { $out: COLL_FF_DEST }]).next(); if (shippingCopyResp) throw new Error("Error in copying Shipping collection"); // above command returns null if successful - progress(50); + progress(20); const flatRateRestCopyResp = await db.collection(COLL_FFR_SOURCE).aggregate([{ $match: {} }, { $out: COLL_FFR_DEST }]).next(); if (flatRateRestCopyResp) throw new Error("Error in copying FlatRateFulfillmentRestrictions collection"); // above command returns null if successful + progress(40); + + const operations = []; + + const ffTypeUpdate = { + updateMany: { + filter: { fulfillmentType: { $exists: false } }, + update: { + $set: { + fulfillmentType: FF_TYPE + } + } + } + }; + + const ffMethodUpdate = { + updateMany: { + filter: { methods: { $exists: true } }, + update: { + $set: { + "methods.$[eachMethod].fulfillmentMethod": FF_METHOD + } + }, + arrayFilters: [ + { + "eachMethod.fulfillmentMethod": { $exists: false } + } + ] + } + }; + + operations.push(ffTypeUpdate); + operations.push(ffMethodUpdate); + + const bulkWriteResp = await db.collection(COLL_DEST).bulkWrite(operations); + if (bulkWriteResp.writeErrors && bulkWriteResp.writeErrors.length) throw new Error("Error while updating Fulfillment collection"); + progress(50); + + const oldPerms = [ + "reaction:legacy:shippingMethods/create", + "reaction:legacy:shippingMethods/delete", + "reaction:legacy:shippingMethods/read", + "reaction:legacy:shippingMethods/update", + "reaction:legacy:shippingRestrictions/create", + "reaction:legacy:shippingRestrictions/delete", + "reaction:legacy:shippingRestrictions/read", + "reaction:legacy:shippingRestrictions/update" + ]; + + const mapperSetFFMethodsRestricts = { + "reaction:legacy:shippingMethods/create": "reaction:legacy:fulfillmentMethods/create", + "reaction:legacy:shippingMethods/delete": "reaction:legacy:fulfillmentMethods/delete", + "reaction:legacy:shippingMethods/read": "reaction:legacy:fulfillmentMethods/read", + "reaction:legacy:shippingMethods/update": "reaction:legacy:fulfillmentMethods/update", + "reaction:legacy:shippingRestrictions/create": "reaction:legacy:fulfillmentRestrictions/create", + "reaction:legacy:shippingRestrictions/delete": "reaction:legacy:fulfillmentRestrictions/delete", + "reaction:legacy:shippingRestrictions/read": "reaction:legacy:fulfillmentRestrictions/read", + "reaction:legacy:shippingRestrictions/update": "reaction:legacy:fulfillmentRestrictions/update" + }; + const mapperSetFFTypes = { + "reaction:legacy:shippingMethods/create": "reaction:legacy:fulfillmentTypes/create", + "reaction:legacy:shippingMethods/delete": "reaction:legacy:fulfillmentTypes/delete", + "reaction:legacy:shippingMethods/read": "reaction:legacy:fulfillmentTypes/read", + "reaction:legacy:shippingMethods/update": "reaction:legacy:fulfillmentTypes/update" + }; + const allGroups = await db.collection("Groups").find({}).toArray(); + + for (let index = 0; index < allGroups.length; index += 1) { + const currentGroup = allGroups[index]; + const currentPerms = currentGroup.permissions; + const permsToAdd = []; + + if (currentPerms && Array.isArray(currentPerms) && currentPerms.length) { + oldPerms.forEach((oldPerm) => { + if (currentPerms.includes(oldPerm)) { + permsToAdd.push(mapperSetFFMethodsRestricts[oldPerm]); + if (mapperSetFFTypes[oldPerm]) { + permsToAdd.push(mapperSetFFTypes[oldPerm]); + } + } + }); + } + if (permsToAdd.length) { + permsToAdd.push("reaction:legacy:fulfillmentTypes/update:settings"); // add this setting to groups deailing with ff-types + // eslint-disable-next-line no-await-in-loop + await db.collection("Groups").updateOne( + { _id: currentGroup._id }, + { + $addToSet: { permissions: { $each: permsToAdd } } + } + ); + } + } + progress(100); } diff --git a/packages/api-plugin-fulfillment/migrations/3.js b/packages/api-plugin-fulfillment/migrations/3.js deleted file mode 100644 index e1b1506034e..00000000000 --- a/packages/api-plugin-fulfillment/migrations/3.js +++ /dev/null @@ -1,56 +0,0 @@ -const FF_TYPE = "shipping"; -const FF_METHOD = "flatRate"; -const COLL_DEST = "Fulfillment"; - -/** - * @summary Performs migration up from previous data version - * @param {Object} context Migration context - * @param {Object} context.db MongoDB `Db` instance - * @param {Function} context.progress A function to report progress, takes percent - * number as argument. - * @return {undefined} - */ -async function up({ db, progress }) { - progress(0); - - const operations = []; - - const ffTypeUpdate = { - updateMany: { - filter: { fulfillmentType: { $exists: false } }, - update: { - $set: { - fulfillmentType: FF_TYPE - } - } - } - }; - - const ffMethodUpdate = { - updateMany: { - filter: { methods: { $exists: true } }, - update: { - $set: { - "methods.$[eachMethod].fulfillmentMethod": FF_METHOD - } - }, - arrayFilters: [ - { - "eachMethod.fulfillmentMethod": { $exists: false } - } - ] - } - }; - - operations.push(ffTypeUpdate); - operations.push(ffMethodUpdate); - - const bulkWriteResp = await db.collection(COLL_DEST).bulkWrite(operations); - if (bulkWriteResp.writeErrors && bulkWriteResp.writeErrors.length) throw new Error("Error while updating Fulfillment collection"); - progress(100); -} - -export default { - down: "unnecessary", // The newly created collections would get dropped in stage2 - up -}; diff --git a/packages/api-plugin-fulfillment/migrations/4.js b/packages/api-plugin-fulfillment/migrations/4.js deleted file mode 100644 index af2dda9bbdf..00000000000 --- a/packages/api-plugin-fulfillment/migrations/4.js +++ /dev/null @@ -1,64 +0,0 @@ -const affectedGlobalGroups = [ - "owner", - "shop manager" -]; - -const newGlobalPermissions = [ - "reaction:legacy:fulfillmentRestrictions/create", - "reaction:legacy:fulfillmentRestrictions/delete", - "reaction:legacy:fulfillmentRestrictions/read", - "reaction:legacy:fulfillmentRestrictions/update", - "reaction:legacy:fulfillmentTypes/create", - "reaction:legacy:fulfillmentTypes/delete", - "reaction:legacy:fulfillmentTypes/read", - "reaction:legacy:fulfillmentTypes/update", - "reaction:legacy:fulfillmentMethods/create", - "reaction:legacy:fulfillmentMethods/delete", - "reaction:legacy:fulfillmentMethods/read", - "reaction:legacy:fulfillmentMethods/update" -]; - -/** - * @summary migrates the database down one version - * @param {Object} context Migration context - * @param {Object} context.db MongoDB `Db` instance - * @param {Function} context.progress A function to report progress, takes percent - * number as argument. - * @return {undefined} - */ -async function down({ db, progress }) { - progress(0); - - await db.collection("Groups").updateMany({ - slug: { $in: affectedGlobalGroups } - }, { - $pullAll: { permissions: newGlobalPermissions } - }); - - progress(100); -} - -/** - * @summary Performs migration up from previous data version - * @param {Object} context Migration context - * @param {Object} context.db MongoDB `Db` instance - * @param {Function} context.progress A function to report progress, takes percent - * number as argument. - * @return {undefined} - */ -async function up({ db, progress }) { - progress(0); - - await db.collection("Groups").updateMany({ - slug: { $in: affectedGlobalGroups } - }, { - $addToSet: { permissions: { $each: newGlobalPermissions } } - }); - - progress(100); -} - -export default { - down, - up -}; diff --git a/packages/api-plugin-fulfillment/migrations/index.js b/packages/api-plugin-fulfillment/migrations/index.js index 783e2bf9115..d6ef9ab5586 100644 --- a/packages/api-plugin-fulfillment/migrations/index.js +++ b/packages/api-plugin-fulfillment/migrations/index.js @@ -1,16 +1,12 @@ import { migrationsNamespace } from "./migrationsNamespace.js"; import migration2 from "./2.js"; -import migration3 from "./3.js"; -import migration4 from "./4.js"; export default { tracks: [ { namespace: migrationsNamespace, migrations: { - 2: migration2, - 3: migration3, - 4: migration4 + 2: migration2 } } ] From 3404407f5f414e87f51eb08fadbbf34788ed8252 Mon Sep 17 00:00:00 2001 From: Sujith Date: Mon, 7 Nov 2022 19:15:43 +0530 Subject: [PATCH 45/71] fix: pnpm-lock without snyk Signed-off-by: Sujith --- pnpm-lock.yaml | 42 ++---------------------------------------- 1 file changed, 2 insertions(+), 40 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a54914d1c58..2ea48d291c5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -678,6 +678,7 @@ importers: '@reactioncommerce/api-plugin-carts': ^1.0.0 '@reactioncommerce/api-utils': ^1.16.9 '@reactioncommerce/data-factory': ~1.0.1 + '@reactioncommerce/db-version-check': ^1.0.0 '@reactioncommerce/logger': ^1.1.3 '@reactioncommerce/random': ^1.0.2 '@reactioncommerce/reaction-error': ^1.0.1 @@ -685,6 +686,7 @@ importers: simpl-schema: ^1.12.2 dependencies: '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/db-version-check': link:../db-version-check '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error @@ -694,46 +696,6 @@ importers: '@reactioncommerce/api-plugin-carts': link:../api-plugin-carts '@reactioncommerce/data-factory': 1.0.1 - packages/api-plugin-fulfillment-method-shipping-flat-rate: - specifiers: - '@reactioncommerce/api-utils': ^1.16.9 - '@reactioncommerce/logger': ^1.1.3 - '@reactioncommerce/random': ~1.0.2 - '@reactioncommerce/reaction-error': ^1.0.1 - simpl-schema: ^1.12.2 - dependencies: - '@reactioncommerce/api-utils': link:../api-utils - '@reactioncommerce/logger': link:../logger - '@reactioncommerce/random': link:../random - '@reactioncommerce/reaction-error': link:../reaction-error - simpl-schema: 1.12.3 - - packages/api-plugin-fulfillment-method-shipping-ups: - specifiers: - '@reactioncommerce/api-utils': ^1.16.9 - '@reactioncommerce/reaction-error': ^1.0.1 - simpl-schema: ^1.12.2 - dependencies: - '@reactioncommerce/api-utils': link:../api-utils - '@reactioncommerce/reaction-error': link:../reaction-error - simpl-schema: 1.12.3 - - packages/api-plugin-fulfillment-type-shipping: - specifiers: - '@reactioncommerce/api-utils': ^1.16.9 - '@reactioncommerce/logger': ^1.1.3 - '@reactioncommerce/random': ~1.0.2 - '@reactioncommerce/reaction-error': ^1.0.1 - lodash: ^4.17.21 - simpl-schema: ^1.12.2 - dependencies: - '@reactioncommerce/api-utils': link:../api-utils - '@reactioncommerce/logger': link:../logger - '@reactioncommerce/random': link:../random - '@reactioncommerce/reaction-error': link:../reaction-error - lodash: 4.17.21 - simpl-schema: 1.12.3 - packages/api-plugin-i18n: specifiers: '@reactioncommerce/api-utils': ^1.16.5 From 278c8d4351e6d5a5b19c83d96327555061a1895c Mon Sep 17 00:00:00 2001 From: Sujith Date: Thu, 10 Nov 2022 18:56:08 +0530 Subject: [PATCH 46/71] fix: review comment fixes 1 Signed-off-by: Sujith --- .../createFulfillmentMethod.test.js.snap | 3 - .../createFulfillmentType.test.js.snap | 3 - ...lectFulfillmentOptionForGroup.test.js.snap | 5 -- .../updateFulfillmentMethod.test.js.snap | 3 - ...ateFulfillmentOptionsForGroup.test.js.snap | 3 - .../updateFulfillmentType.test.js.snap | 3 - .../src/mutations/createFulfillmentMethod.js | 29 +++++----- .../mutations/createFulfillmentMethod.test.js | 5 +- .../src/mutations/createFulfillmentType.js | 15 +++-- .../mutations/createFulfillmentType.test.js | 7 ++- .../selectFulfillmentOptionForGroup.test.js | 6 +- .../src/mutations/updateFulfillmentMethod.js | 23 ++------ .../mutations/updateFulfillmentMethod.test.js | 6 +- .../updateFulfillmentOptionsForGroup.test.js | 3 +- .../src/mutations/updateFulfillmentType.js | 58 +++++++++++-------- .../mutations/updateFulfillmentType.test.js | 8 +-- .../api-plugin-fulfillment/src/preStartup.js | 10 ---- ...llmentMethods.js => fulfillmentMethods.js} | 4 +- .../queries/fulfillmentMethodsWithQuotes.js | 51 ++++++++++++++++ ...tFulfillmentType.js => fulfillmentType.js} | 4 +- ...ulfillmentTypes.js => fulfillmentTypes.js} | 4 +- .../getFulfillmentMethodsWithQuotes.js | 44 ++------------ .../src/queries/index.js | 14 +++-- .../Mutation/updateFulfillmentMethod.js | 12 +--- .../Mutation/updateFulfillmentType.js | 11 +--- ...llmentMethods.js => fulfillmentMethods.js} | 12 ++-- ...tFulfillmentType.js => fulfillmentType.js} | 16 ++--- ...ulfillmentTypes.js => fulfillmentTypes.js} | 9 +-- .../src/resolvers/Query/index.js | 12 ++-- .../src/schemas/schema.graphql | 26 +++------ .../src/simpleSchemas.js | 47 +++++++++++++++ 31 files changed, 229 insertions(+), 227 deletions(-) delete mode 100644 packages/api-plugin-fulfillment/src/mutations/__snapshots__/createFulfillmentMethod.test.js.snap delete mode 100644 packages/api-plugin-fulfillment/src/mutations/__snapshots__/createFulfillmentType.test.js.snap delete mode 100644 packages/api-plugin-fulfillment/src/mutations/__snapshots__/selectFulfillmentOptionForGroup.test.js.snap delete mode 100644 packages/api-plugin-fulfillment/src/mutations/__snapshots__/updateFulfillmentMethod.test.js.snap delete mode 100644 packages/api-plugin-fulfillment/src/mutations/__snapshots__/updateFulfillmentOptionsForGroup.test.js.snap delete mode 100644 packages/api-plugin-fulfillment/src/mutations/__snapshots__/updateFulfillmentType.test.js.snap rename packages/api-plugin-fulfillment/src/queries/{getFulfillmentMethods.js => fulfillmentMethods.js} (92%) create mode 100644 packages/api-plugin-fulfillment/src/queries/fulfillmentMethodsWithQuotes.js rename packages/api-plugin-fulfillment/src/queries/{getFulfillmentType.js => fulfillmentType.js} (88%) rename packages/api-plugin-fulfillment/src/queries/{getFulfillmentTypes.js => fulfillmentTypes.js} (85%) rename packages/api-plugin-fulfillment/src/resolvers/Query/{getFulfillmentMethods.js => fulfillmentMethods.js} (65%) rename packages/api-plugin-fulfillment/src/resolvers/Query/{getFulfillmentType.js => fulfillmentType.js} (52%) rename packages/api-plugin-fulfillment/src/resolvers/Query/{getFulfillmentTypes.js => fulfillmentTypes.js} (75%) diff --git a/packages/api-plugin-fulfillment/src/mutations/__snapshots__/createFulfillmentMethod.test.js.snap b/packages/api-plugin-fulfillment/src/mutations/__snapshots__/createFulfillmentMethod.test.js.snap deleted file mode 100644 index 79e2bc2c52b..00000000000 --- a/packages/api-plugin-fulfillment/src/mutations/__snapshots__/createFulfillmentMethod.test.js.snap +++ /dev/null @@ -1,3 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`throws if required fields are not supplied 1`] = `"Fulfillment types is required"`; diff --git a/packages/api-plugin-fulfillment/src/mutations/__snapshots__/createFulfillmentType.test.js.snap b/packages/api-plugin-fulfillment/src/mutations/__snapshots__/createFulfillmentType.test.js.snap deleted file mode 100644 index 828ff99c293..00000000000 --- a/packages/api-plugin-fulfillment/src/mutations/__snapshots__/createFulfillmentType.test.js.snap +++ /dev/null @@ -1,3 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`throws if required fields are not supplied 1`] = `"Enabled is required"`; diff --git a/packages/api-plugin-fulfillment/src/mutations/__snapshots__/selectFulfillmentOptionForGroup.test.js.snap b/packages/api-plugin-fulfillment/src/mutations/__snapshots__/selectFulfillmentOptionForGroup.test.js.snap deleted file mode 100644 index c47469bdf28..00000000000 --- a/packages/api-plugin-fulfillment/src/mutations/__snapshots__/selectFulfillmentOptionForGroup.test.js.snap +++ /dev/null @@ -1,5 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`throws if there is no fulfillment group with the given ID 1`] = `"Fulfillment group with ID group2 not found in cart with ID cartId"`; - -exports[`throws if there is no fulfillment method with the given ID among the options 1`] = `"Fulfillment option with method ID invalid-method not found in cart with ID cartId"`; diff --git a/packages/api-plugin-fulfillment/src/mutations/__snapshots__/updateFulfillmentMethod.test.js.snap b/packages/api-plugin-fulfillment/src/mutations/__snapshots__/updateFulfillmentMethod.test.js.snap deleted file mode 100644 index d165ef0089d..00000000000 --- a/packages/api-plugin-fulfillment/src/mutations/__snapshots__/updateFulfillmentMethod.test.js.snap +++ /dev/null @@ -1,3 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`throws if required fields are not supplied 1`] = `"Method ID is required"`; diff --git a/packages/api-plugin-fulfillment/src/mutations/__snapshots__/updateFulfillmentOptionsForGroup.test.js.snap b/packages/api-plugin-fulfillment/src/mutations/__snapshots__/updateFulfillmentOptionsForGroup.test.js.snap deleted file mode 100644 index 45e9c989519..00000000000 --- a/packages/api-plugin-fulfillment/src/mutations/__snapshots__/updateFulfillmentOptionsForGroup.test.js.snap +++ /dev/null @@ -1,3 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`throws if there is no fulfillment group with the given ID 1`] = `"Fulfillment group with ID group2 not found in cart with ID cartId"`; diff --git a/packages/api-plugin-fulfillment/src/mutations/__snapshots__/updateFulfillmentType.test.js.snap b/packages/api-plugin-fulfillment/src/mutations/__snapshots__/updateFulfillmentType.test.js.snap deleted file mode 100644 index 696b1da29dc..00000000000 --- a/packages/api-plugin-fulfillment/src/mutations/__snapshots__/updateFulfillmentType.test.js.snap +++ /dev/null @@ -1,3 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`throws if required fields are not supplied 1`] = `"Name is required"`; diff --git a/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.js b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.js index 0d1226c9ad9..75368c763d4 100644 --- a/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.js +++ b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.js @@ -14,15 +14,15 @@ const inputSchema = new SimpleSchema({ * @summary Creates a fulfillment method * @param {Object} context - an object containing the per-request state * @param {Object} input - Input - * @param {Number} cost - optional cost - * @param {String[]} fulfillmentTypes - fulfillment type retained for backward compatibility - * @param {String} group - Group to which fulfillment method belong - * @param {Number} handling - handling charges - * @param {Boolean} enabled - status of fulfillment method - * @param {String} label - displayed on the UI - * @param {String} fulfillmentMethod - used by application, not user editable - * @param {String} displayMessageMethod - used to display additional message on UI specific to ff-method - * @param {Number} rate - ratefor themethod + * @param {Number} input.cost - optional cost + * @param {String[]} input.fulfillmentTypes - fulfillment type retained for backward compatibility + * @param {String} input.group - Group to which fulfillment method belong + * @param {Number} input.handling - handling charges + * @param {Boolean} input.enabled - status of fulfillment method + * @param {String} input.label - displayed on the UI + * @param {String} input.fulfillmentMethod - used by application, not user editable + * @param {String} input.displayMessageMethod - used to display additional message on UI specific to ff-method + * @param {Number} input.rate - rate for the method * @returns {Promise} An object with a `method` property containing the created method */ export default async function createFulfillmentMethodMutation(context, input) { @@ -38,11 +38,10 @@ export default async function createFulfillmentMethodMutation(context, input) { const ffTypeRecord = await Fulfillment.findOne({ _id: fulfillmentTypeId, shopId }); if (!ffTypeRecord) throw new ReactionError("server-error", "Unable to create fulfillment method without defined type"); - const ffTypeMethodRecord = await Fulfillment.findOne({ - _id: fulfillmentTypeId, - shopId, - methods: { $elemMatch: { fulfillmentMethod: method.fulfillmentMethod } } - }); + let ffTypeMethodRecord; + if (ffTypeRecord.methods && Array.isArray(ffTypeRecord.methods)) { + ffTypeMethodRecord = ffTypeRecord.methods.find((currMethod) => currMethod.fulfillmentMethod === method.fulfillmentMethod); + } if (ffTypeMethodRecord) throw new ReactionError("server-error", "Fulfillment Method already exists"); method._id = Random.id(); @@ -59,5 +58,5 @@ export default async function createFulfillmentMethodMutation(context, input) { if (matchedCount === 0) throw new ReactionError("server-error", "Unable to create fulfillment method"); - return { group: method }; + return { method }; } diff --git a/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.test.js b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.test.js index 9248b986a28..8da72ee66f0 100644 --- a/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.test.js +++ b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.test.js @@ -18,7 +18,8 @@ test("throws if required fields are not supplied", async () => { shopId: "SHOP_ID" }; - await expect(createFulfillmentMethodMutation(mockContext, fulfillmentMethodInput)).rejects.toThrowErrorMatchingSnapshot(); + const expectedError = "Fulfillment types is required"; + await expect(createFulfillmentMethodMutation(mockContext, fulfillmentMethodInput)).rejects.toThrow(expectedError); }); test("add a new fulfillment method", async () => { @@ -55,7 +56,7 @@ test("add a new fulfillment method", async () => { }); expect(result).toEqual({ - group: { + method: { _id: expect.any(String), cost: 99, handling: 99, diff --git a/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.js b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.js index c0f8ffff0c6..889c33ee6bf 100644 --- a/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.js +++ b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.js @@ -12,18 +12,21 @@ import { FulfillmentTypeSchema } from "../simpleSchemas.js"; * @param {ProviderSchema} input.provider - Provider details * @param {String} input.fulfillmentType - fulfillment type * @param {String} input.displayMessageType - displayMessage for fulfillment type - * @param {FulfillmentMethodSchema[]} input.methods - ff-method array * @returns {Promise} An object with the updated type */ export default async function createFulfillmentType(context, input) { const cleanedInput = FulfillmentTypeSchema.clean(input); - if (!cleanedInput.provider) cleanedInput.provider = {}; - cleanedInput.provider.name = cleanedInput.name; + if (cleanedInput.provider) cleanedInput.provider.name = cleanedInput.name; - if (cleanedInput.method) { - cleanedInput.method._id = Random.id(); - cleanedInput.method.fulfillmentTypes = [cleanedInput.fulfillmentType]; + // Although allowed by schema, we do not add the ff-method while creating a new ff-type + // FulfillmentMethods are expected to be added using the mutation createFulfillmentMethod + // (as it makes sense to add a new ff-method only by a new plugin implementation). + if (cleanedInput.methods) { + delete cleanedInput.methods; } + const createdAt = new Date(); + cleanedInput.createdAt = createdAt; + cleanedInput.updatedAt = createdAt; FulfillmentTypeSchema.validate(cleanedInput); const { collections: { Fulfillment } } = context; diff --git a/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.test.js b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.test.js index c14d85fb3a8..9ecef83c38b 100644 --- a/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.test.js +++ b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.test.js @@ -9,11 +9,12 @@ mockContext.validatePermissions.mockReturnValueOnce(Promise.resolve(null)); test("throws if required fields are not supplied", async () => { const fulfillmentTypeInput = { - shopId: "SHOP_ID", - name: "fulfillmentType123" + shopId: "SHOP_ID" + // name: "fulfillmentType123" }; - await expect(createFulfillmentTypeMutation(mockContext, fulfillmentTypeInput)).rejects.toThrowErrorMatchingSnapshot(); + const expectedError = "Name is required"; + await expect(createFulfillmentTypeMutation(mockContext, fulfillmentTypeInput)).rejects.toThrow(expectedError); }); test("throws if the fulfillmentType added already exists", async () => { diff --git a/packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.test.js b/packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.test.js index a526864913d..7bea01b8200 100644 --- a/packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.test.js +++ b/packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.test.js @@ -53,17 +53,19 @@ test("selects an existing shipping method", async () => { }); test("throws if there is no fulfillment group with the given ID", async () => { + const expectedError = "Fulfillment group with ID group2 not found in cart with ID cartId"; await expect(selectFulfillmentOptionForGroup(mockContext, { cartId: "cartId", fulfillmentGroupId: "group2", fulfillmentMethodId: "valid-method" - })).rejects.toThrowErrorMatchingSnapshot(); + })).rejects.toThrow(expectedError); }); test("throws if there is no fulfillment method with the given ID among the options", async () => { + const expectedError = "Fulfillment option with method ID invalid-method not found in cart with ID cartId"; await expect(selectFulfillmentOptionForGroup(mockContext, { cartId: "cartId", fulfillmentGroupId: "group1", fulfillmentMethodId: "invalid-method" - })).rejects.toThrowErrorMatchingSnapshot(); + })).rejects.toThrow(expectedError); }); diff --git a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.js b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.js index 45308082112..67e78fa97cc 100644 --- a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.js +++ b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.js @@ -1,9 +1,10 @@ import SimpleSchema from "simpl-schema"; import ReactionError from "@reactioncommerce/reaction-error"; -import { FulfillmentMethodSchema } from "../simpleSchemas.js"; +import { UserEditableFulfillmentMethodSchema } from "../simpleSchemas.js"; + const inputSchema = new SimpleSchema({ - method: FulfillmentMethodSchema, + method: UserEditableFulfillmentMethodSchema, fulfillmentTypeId: String, methodId: String, shopId: String @@ -14,7 +15,7 @@ const inputSchema = new SimpleSchema({ * @summary updates Fulfillment method * @param {Object} context - an object containing the per-request state * @param {Object} input - Input object - * @param {FulfillmentMethodSchema} input.method - fulfillment method object + * @param {UserEditableFulfillmentMethodSchema} input.method - fulfillment method object with only user editable fields * @param {String} input.fulfillmentTypeId - id of fulfillment type * @param {String} input.methodId - ff-method Id * @param {String} input.shopId - Shop Id @@ -28,9 +29,6 @@ export default async function updateFulfillmentMethodMutation(context, input) { const { collections: { Fulfillment } } = context; const method = { ...inputMethod }; - if (!fulfillmentTypeId) throw new ReactionError("invalid-parameter", "Fulfillment Type ID to be updated not provided"); - if (!methodId) throw new ReactionError("invalid-parameter", "Method ID to be updated not provided"); - await context.validatePermissions(`reaction:legacy:fulfillmentMethods:${methodId}`, "update", { shopId }); const ffTypeMethodRecord = await Fulfillment.findOne({ @@ -40,18 +38,9 @@ export default async function updateFulfillmentMethodMutation(context, input) { }); if (!ffTypeMethodRecord) throw new ReactionError("server-error", "Fulfillment Method does not exist"); - // Do not update the fulfillmentType, fulfillmentMethod, group, name & _id fields - // Find the matching fulfillmentMethod object and use those values to over-write const currentFulfillmentMethod = (ffTypeMethodRecord.methods || []).find((meth) => meth._id === methodId); if (!currentFulfillmentMethod) throw new ReactionError("server-error", "Fulfillment Method does not exist"); - const updatedMethod = { - ...method, - _id: methodId, - name: currentFulfillmentMethod.name, - group: currentFulfillmentMethod.group, - fulfillmentMethod: currentFulfillmentMethod.fulfillmentMethod, - fulfillmentType: [ffTypeMethodRecord.fulfillmentType] - }; + const updatedMethod = { ...currentFulfillmentMethod, ...method }; // update only provided user editable fields const { matchedCount } = await Fulfillment.updateOne({ "_id": fulfillmentTypeId, @@ -64,5 +53,5 @@ export default async function updateFulfillmentMethodMutation(context, input) { }); if (matchedCount === 0) throw new ReactionError("not-found", "Fulfillment type to be updated not found"); - return { group: updatedMethod }; + return { method: updatedMethod }; } diff --git a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.test.js b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.test.js index 0a230020809..6c9df48dba9 100644 --- a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.test.js +++ b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.test.js @@ -22,7 +22,8 @@ test("throws if required fields are not supplied", async () => { } }; - await expect(updateFulfillmentMethodMutation(mockContext, fulfillmentMethodInput)).rejects.toThrowErrorMatchingSnapshot(); + const expectedError = "Method ID is required"; + await expect(updateFulfillmentMethodMutation(mockContext, fulfillmentMethodInput)).rejects.toThrow(expectedError); }); test("throws if the fulfillmentType does not exists", async () => { @@ -116,11 +117,10 @@ test("should update an existing fulfillment method", async () => { }; const expectedOutput = { - group: { + method: { _id: "fulfillmentMethodId01", enabled: true, fulfillmentMethod: "ups", - fulfillmentType: ["Shipping"], fulfillmentTypes: ["Shipping"], group: "Ground", handling: 99, diff --git a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.test.js b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.test.js index 557637fa626..1553742d6cd 100644 --- a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.test.js +++ b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.test.js @@ -165,8 +165,9 @@ test("updates cart properly for success rates", async () => { }); test("throws if there is no fulfillment group with the given ID", async () => { + const expectedError = "Fulfillment group with ID group2 not found in cart with ID cartId"; await expect(updateFulfillmentOptionsForGroup(mockContext, { cartId: "cartId", fulfillmentGroupId: "group2" - })).rejects.toThrowErrorMatchingSnapshot(); + })).rejects.toThrow(expectedError); }); diff --git a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.js b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.js index 41c21aef41a..256225265a4 100644 --- a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.js +++ b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.js @@ -2,14 +2,20 @@ import SimpleSchema from "simpl-schema"; import ReactionError from "@reactioncommerce/reaction-error"; const inputSchema = new SimpleSchema({ - fulfillmentGroupId: String, + fulfillmentTypeId: String, shopId: String, - name: String, + name: { + type: String, + optional: true + }, enabled: { type: Boolean, - defaultValue: true + optional: true + }, + label: { + type: String, + optional: true }, - label: String, displayMessageType: { type: String, optional: true @@ -21,7 +27,7 @@ const inputSchema = new SimpleSchema({ * @summary updates the selected fulfillment type * @param {Object} context - an object containing the per-request state * @param {Object} input - Input object - * @param {String} input.fulfillmentGroupId - fulfillment tpe id of group + * @param {String} input.fulfillmentTypeId - fulfillment tpe id of group * @param {String} input.shopId - Shop Id * @param {String} input.name - name of fulfillment type * @param {Boolean} input.enabled - status of ff-type @@ -33,28 +39,32 @@ export default async function updateFulfillmentType(context, input) { const cleanedInput = inputSchema.clean(input); // add default values and such inputSchema.validate(cleanedInput); - const { fulfillmentGroupId, shopId, name, enabled, label, displayMessageType } = cleanedInput; + const { fulfillmentTypeId, shopId, name, enabled, label, displayMessageType } = cleanedInput; const { collections: { Fulfillment } } = context; - if (!shopId) throw new ReactionError("invalid-parameter", "Shop ID to be updated not provided"); - if (!fulfillmentGroupId) throw new ReactionError("invalid-parameter", "FulfillmentType ID to be updated not provided"); - if (!name) throw new ReactionError("invalid-parameter", "FulfillmentType Name to be updated not provided"); - - await context.validatePermissions(`reaction:legacy:fulfillmentTypes:${fulfillmentGroupId}`, "update", { shopId }); + await context.validatePermissions(`reaction:legacy:fulfillmentTypes:${fulfillmentTypeId}`, "update", { shopId }); - const { matchedCount } = await Fulfillment.updateOne({ - _id: fulfillmentGroupId, - shopId - }, { - $set: { - name, - "provider.enabled": enabled, - "provider.name": name, - "provider.label": label, - displayMessageType - } - }); - if (matchedCount === 0) throw new ReactionError("not-found", "Fulfillment type to update not found"); + const updatedAt = new Date(); + const providerObject = {}; + const updateObject = {}; + if (enabled) providerObject.enabled = enabled; + if (name) providerObject.name = name; + if (label) providerObject.label = label; + if (Object.keys(providerObject).length) { + updateObject.provider = providerObject; + } + if (displayMessageType) updateObject.displayMessageType = displayMessageType; + if (name) updateObject.name = name; + if (Object.keys(updateObject).length) { + updateObject.updatedAt = updatedAt; + const { matchedCount } = await Fulfillment.updateOne({ + _id: fulfillmentTypeId, + shopId + }, { + $set: updateObject + }); + if (matchedCount === 0) throw new ReactionError("not-found", "Fulfillment type to update not found"); + } return { group: cleanedInput }; } diff --git a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.test.js b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.test.js index 30402070311..97442bd67f8 100644 --- a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.test.js +++ b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.test.js @@ -8,12 +8,12 @@ mockContext.validatePermissions.mockReturnValueOnce(Promise.resolve(null)); test("throws if required fields are not supplied", async () => { const fulfillmentTypeInput = { - fulfillmentGroupId: "fulfillmentGroup01", shopId: "SHOP_ID", label: "Shipping" }; - await expect(updateFulfillmentTypeMutation(mockContext, fulfillmentTypeInput)).rejects.toThrowErrorMatchingSnapshot(); + const expectedError = "Fulfillment type ID is required"; + await expect(updateFulfillmentTypeMutation(mockContext, fulfillmentTypeInput)).rejects.toThrow(expectedError); }); @@ -21,7 +21,7 @@ test("should update an existing fulfillment type", async () => { mockContext.collections.Fulfillment.updateOne.mockReturnValueOnce(Promise.resolve({ result: { matchedCount: 1 } })); const fulfillmentTypeInput = { - fulfillmentGroupId: "fulfillmentGroup01", + fulfillmentTypeId: "fulfillmentGroup01", shopId: "SHOP_ID", name: "fulfillmentType123", enabled: false, @@ -29,7 +29,7 @@ test("should update an existing fulfillment type", async () => { }; const expectedOutput = { - fulfillmentGroupId: "fulfillmentGroup01", + fulfillmentTypeId: "fulfillmentGroup01", shopId: "SHOP_ID", name: "fulfillmentType123", enabled: false, diff --git a/packages/api-plugin-fulfillment/src/preStartup.js b/packages/api-plugin-fulfillment/src/preStartup.js index e65d3743063..f508339dde1 100644 --- a/packages/api-plugin-fulfillment/src/preStartup.js +++ b/packages/api-plugin-fulfillment/src/preStartup.js @@ -1,9 +1,5 @@ -import Logger from "@reactioncommerce/logger"; -import ReactionError from "@reactioncommerce/reaction-error"; import { extendFulfillmentSchemas } from "./simpleSchemas.js"; -const logCtx = { name: "fulfillment", file: "preStartup" }; - /** * @summary Called before startup to extend schemas * @param {Object} context Startup context @@ -11,11 +7,5 @@ const logCtx = { name: "fulfillment", file: "preStartup" }; */ export default async function fulfillmentPreStartup(context) { const allFulfillmentTypesArray = context.fulfillment?.registeredFulfillmentTypes; - - if (!allFulfillmentTypesArray || allFulfillmentTypesArray.length === 0) { - Logger.warn(logCtx, "No fulfillment types available"); - throw new ReactionError("not-configured", "No fulfillment types available"); - } - extendFulfillmentSchemas(context.simpleSchemas, allFulfillmentTypesArray); } diff --git a/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethods.js b/packages/api-plugin-fulfillment/src/queries/fulfillmentMethods.js similarity index 92% rename from packages/api-plugin-fulfillment/src/queries/getFulfillmentMethods.js rename to packages/api-plugin-fulfillment/src/queries/fulfillmentMethods.js index eedfa584ccb..fe5e3904d2f 100644 --- a/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethods.js +++ b/packages/api-plugin-fulfillment/src/queries/fulfillmentMethods.js @@ -1,5 +1,5 @@ /** - * @name getFulfillmentMethods + * @name fulfillmentMethods * @method * @memberof Fulfillment/Queries * @summary Query the Fulfillment collection for a list of fulfillment Methods @@ -9,7 +9,7 @@ * @param {String} input.fulfillmentTypeId - The fulfillmentType id of the fulfillment type * @returns {Promise} Mongo cursor */ -export default async function getFulfillmentMethods(context, input) { +export default async function fulfillmentMethods(context, input) { const { collections: { Fulfillment } } = context; const { shopId, fulfillmentTypeId } = input; diff --git a/packages/api-plugin-fulfillment/src/queries/fulfillmentMethodsWithQuotes.js b/packages/api-plugin-fulfillment/src/queries/fulfillmentMethodsWithQuotes.js new file mode 100644 index 00000000000..cb9065dcf3f --- /dev/null +++ b/packages/api-plugin-fulfillment/src/queries/fulfillmentMethodsWithQuotes.js @@ -0,0 +1,51 @@ +import Logger from "@reactioncommerce/logger"; +import ReactionError from "@reactioncommerce/reaction-error"; +import extendCommonOrder from "../util/extendCommonOrder.js"; + +const logCtx = { name: "fulfillment", file: "fulfillmentMethodsWithQuotes" }; + +/** + * @name fulfillmentMethodsWithQuotes + * @method + * @summary Just gets rates, without updating anything + * @param {Object} commonOrder - details about the purchase a user wants to make. + * @param {Object} context - Context + * @returns {Array} return updated rates in cart + * @private + */ +export default async function fulfillmentMethodsWithQuotes(commonOrder, context) { + const rates = []; + const retrialTargets = []; + + // must have items to calculate shipping + if (!commonOrder.items || !commonOrder.items.length) { + Logger.debug(logCtx, "fulfillmentMethodsWithQuotes called with CommonOrder with no items"); + return rates; + } + const commonOrderExtended = await extendCommonOrder(context, commonOrder); + + const fulfillmentTypeInGroup = commonOrder.fulfillmentType; + if (!fulfillmentTypeInGroup) throw new ReactionError("not-found", "Fulfillment type not found in commonOrder"); + + const allFuncsArray = context.getFunctionsOfType("fulfillmentMethodsWithQuotes"); + const ffTypeFuncObjects = allFuncsArray.filter((func) => fulfillmentTypeInGroup === func.key); + const funcs = ffTypeFuncObjects.map((func) => func.handler); + + if (!funcs || !Array.isArray(funcs) || !funcs.length) throw new ReactionError("not-found", `No methods for Fulfillment type ${fulfillmentTypeInGroup}`); + + let promises = funcs.map((rateFunction) => rateFunction(context, commonOrderExtended, [rates, retrialTargets])); + await Promise.all(promises); + + // Try once more. + if (retrialTargets.length > 0) { + promises = funcs.map((rateFunction) => rateFunction(context, commonOrderExtended, [rates, retrialTargets])); + await Promise.all(promises); + + if (retrialTargets.length > 0) { + Logger.warn({ ...logCtx, retrialTargets }, "Failed to get fulfillment methods from these packages:"); + } + } + + Logger.debug({ ...logCtx, rates }, "fulfillmentMethodsWithQuotes returning rates"); + return rates; +} diff --git a/packages/api-plugin-fulfillment/src/queries/getFulfillmentType.js b/packages/api-plugin-fulfillment/src/queries/fulfillmentType.js similarity index 88% rename from packages/api-plugin-fulfillment/src/queries/getFulfillmentType.js rename to packages/api-plugin-fulfillment/src/queries/fulfillmentType.js index 81ce44b0276..afe2bdb9022 100644 --- a/packages/api-plugin-fulfillment/src/queries/getFulfillmentType.js +++ b/packages/api-plugin-fulfillment/src/queries/fulfillmentType.js @@ -1,5 +1,5 @@ /** - * @name getFulfillmentType + * @name fulfillmentType * @method * @memberof Fulfillment/Queries * @summary Query the Fulfillment collection for a single fulfillment type @@ -9,7 +9,7 @@ * @param {String} input.shopId - The shop id of the fulfillment type * @returns {Promise} Object */ -export default async function getFulfillmentType(context, input) { +export default async function fulfillmentType(context, input) { const { collections: { Fulfillment } } = context; const { fulfillmentTypeId, shopId } = input; diff --git a/packages/api-plugin-fulfillment/src/queries/getFulfillmentTypes.js b/packages/api-plugin-fulfillment/src/queries/fulfillmentTypes.js similarity index 85% rename from packages/api-plugin-fulfillment/src/queries/getFulfillmentTypes.js rename to packages/api-plugin-fulfillment/src/queries/fulfillmentTypes.js index ba2e20e3ca9..93d6083e8e5 100644 --- a/packages/api-plugin-fulfillment/src/queries/getFulfillmentTypes.js +++ b/packages/api-plugin-fulfillment/src/queries/fulfillmentTypes.js @@ -1,5 +1,5 @@ /** - * @name getFulfillmentTypes + * @name fulfillmentTypes * @method * @memberof Fulfillment/Queries * @summary Query the Fulfillment collection for a list of fulfillment types @@ -8,7 +8,7 @@ * @param {String} input.shopId - The shop id of the fulfillment types * @returns {Promise} Mongo cursor */ -export default async function getFulfillmentTypes(context, input) { +export default async function fulfillmentTypes(context, input) { const { collections: { Fulfillment } } = context; const { shopId } = input; diff --git a/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethodsWithQuotes.js b/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethodsWithQuotes.js index 7c3957ef519..af9da1a1aef 100644 --- a/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethodsWithQuotes.js +++ b/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethodsWithQuotes.js @@ -1,51 +1,15 @@ -import Logger from "@reactioncommerce/logger"; -import ReactionError from "@reactioncommerce/reaction-error"; -import extendCommonOrder from "../util/extendCommonOrder.js"; - -const logCtx = { name: "fulfillment", file: "getFulfillmentMethodsWithQuotes" }; +import fulfillmentMethodsWithQuotes from "./fulfillmentMethodsWithQuotes.js"; /** * @name getFulfillmentMethodsWithQuotes * @method - * @summary Just gets rates, without updating anything + * @summary This is an alias to fulfillmentMethodsWithQuotes for backward compatibility till removal * @param {Object} commonOrder - details about the purchase a user wants to make. * @param {Object} context - Context * @returns {Array} return updated rates in cart * @private + * @deprecated since version 5.0, use fulfillmentMethodsWithQuotes instead */ export default async function getFulfillmentMethodsWithQuotes(commonOrder, context) { - const rates = []; - const retrialTargets = []; - - // must have items to calculate shipping - if (!commonOrder.items || !commonOrder.items.length) { - Logger.debug(logCtx, "getFulfillmentMethodsWithQuotes called with CommonOrder with no items"); - return rates; - } - const commonOrderExtended = await extendCommonOrder(context, commonOrder); - - const fulfillmentTypeInGroup = commonOrder.fulfillmentType; - if (!fulfillmentTypeInGroup) throw new ReactionError("not-found", "Fulfillment type not found in commonOrder"); - - const allFuncsArray = context.getFunctionsOfType("getFulfillmentMethodsWithQuotes"); - const ffTypeFuncObjects = allFuncsArray.filter((func) => fulfillmentTypeInGroup === func.key); - const funcs = ffTypeFuncObjects.map((func) => func.handler); - - if (!funcs || !Array.isArray(funcs) || !funcs.length) throw new ReactionError("not-found", `No methods for Fulfillment type ${fulfillmentTypeInGroup}`); - - let promises = funcs.map((rateFunction) => rateFunction(context, commonOrderExtended, [rates, retrialTargets])); - await Promise.all(promises); - - // Try once more. - if (retrialTargets.length > 0) { - promises = funcs.map((rateFunction) => rateFunction(context, commonOrderExtended, [rates, retrialTargets])); - await Promise.all(promises); - - if (retrialTargets.length > 0) { - Logger.warn({ ...logCtx, retrialTargets }, "Failed to get fulfillment methods from these packages:"); - } - } - - Logger.debug({ ...logCtx, rates }, "getFulfillmentMethodsWithQuotes returning rates"); - return rates; + return fulfillmentMethodsWithQuotes(commonOrder, context); } diff --git a/packages/api-plugin-fulfillment/src/queries/index.js b/packages/api-plugin-fulfillment/src/queries/index.js index 3582994cb92..2c29354e8a8 100644 --- a/packages/api-plugin-fulfillment/src/queries/index.js +++ b/packages/api-plugin-fulfillment/src/queries/index.js @@ -1,12 +1,14 @@ // import allFulfillmentTypes from "./allFulfillmentTypes.js"; import getFulfillmentMethodsWithQuotes from "./getFulfillmentMethodsWithQuotes.js"; -import getFulfillmentType from "./getFulfillmentType.js"; -import getFulfillmentTypes from "./getFulfillmentTypes.js"; -import getFulfillmentMethods from "./getFulfillmentMethods.js"; +import fulfillmentMethodsWithQuotes from "./fulfillmentMethodsWithQuotes.js"; +import fulfillmentType from "./fulfillmentType.js"; +import fulfillmentTypes from "./fulfillmentTypes.js"; +import fulfillmentMethods from "./fulfillmentMethods.js"; export default { - getFulfillmentType, - getFulfillmentTypes, - getFulfillmentMethods, + fulfillmentType, + fulfillmentTypes, + fulfillmentMethods, + fulfillmentMethodsWithQuotes, getFulfillmentMethodsWithQuotes }; diff --git a/packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentMethod.js b/packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentMethod.js index d99e72074f0..c292acf5758 100644 --- a/packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentMethod.js +++ b/packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentMethod.js @@ -1,4 +1,3 @@ -import { decodeShopOpaqueId, decodeFulfillmentGroupOpaqueId, decodeFulfillmentMethodOpaqueId } from "../../xforms/id.js"; import updateFulfillmentMethodMutation from "../../mutations/updateFulfillmentMethod.js"; /** * @name Mutation/updateFulfillmentMethod @@ -11,17 +10,13 @@ import updateFulfillmentMethodMutation from "../../mutations/updateFulfillmentMe * @param {String} args.input.methodId - The fulfillment method to be updated * @param {String} args.input.shopId - The ShopId to which the fulfillment group belongs * @param {String} args.input.method - The fulfillment method data to be updated - * @param {String} [args.input.clientMutationId] - An optional string identifying the mutation call * @param {Object} context - an object containing the per-request state * @returns {Promise} updateFulfillmentMethodPayload */ export default async function updateFulfillmentMethod(parentResult, { input }, context) { - const { shopId: opaqueShopId, clientMutationId = null, fulfillmentTypeId: opaqueFulfillmentTypeId, methodId: opaqueMethodId, method } = input.groupInfo; + const { shopId, fulfillmentTypeId, methodId, method } = input.groupInfo; - const methodId = decodeFulfillmentMethodOpaqueId(opaqueMethodId); - const fulfillmentTypeId = decodeFulfillmentGroupOpaqueId(opaqueFulfillmentTypeId); - const shopId = decodeShopOpaqueId(opaqueShopId); - const { group } = await updateFulfillmentMethodMutation(context, { + const { method: updatedMethod } = await updateFulfillmentMethodMutation(context, { shopId, fulfillmentTypeId, methodId, @@ -29,7 +24,6 @@ export default async function updateFulfillmentMethod(parentResult, { input }, c }); return { - group, - clientMutationId + method: updatedMethod }; } diff --git a/packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentType.js b/packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentType.js index a19ead70dd8..45c3b72e813 100644 --- a/packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentType.js +++ b/packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentType.js @@ -1,4 +1,3 @@ -import { decodeShopOpaqueId, decodeFulfillmentGroupOpaqueId } from "../../xforms/id.js"; import updateFulfillmentTypeMutation from "../../mutations/updateFulfillmentType.js"; /** * @name Mutation/updateFulfillmentType @@ -11,16 +10,13 @@ import updateFulfillmentTypeMutation from "../../mutations/updateFulfillmentType * @param {String} args.input.shopId - The ShopId to which the fulfillment group belongs * @param {String} args.input.name - The fulfillment group name to be updated * @param {String} args.input.enabled - Flag to enable/disable group - * @param {String} [args.input.clientMutationId] - An optional string identifying the mutation call * @param {Object} context - an object containing the per-request state * @returns {Promise} updateFulfillmentTypePayload */ export default async function updateFulfillmentType(parentResult, { input }, context) { - const { groupInfo, clientMutationId = null } = input; - const { shopId: opaqueShopId, fulfillmentGroupId: opaqueFulfillmentGroupId } = groupInfo; + const { groupInfo } = input; + const { shopId, fulfillmentGroupId } = groupInfo; - const fulfillmentGroupId = decodeFulfillmentGroupOpaqueId(opaqueFulfillmentGroupId); - const shopId = decodeShopOpaqueId(opaqueShopId); const { group } = await updateFulfillmentTypeMutation(context, { ...groupInfo, shopId, @@ -28,7 +24,6 @@ export default async function updateFulfillmentType(parentResult, { input }, con }); return { - group, - clientMutationId + group }; } diff --git a/packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentMethods.js b/packages/api-plugin-fulfillment/src/resolvers/Query/fulfillmentMethods.js similarity index 65% rename from packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentMethods.js rename to packages/api-plugin-fulfillment/src/resolvers/Query/fulfillmentMethods.js index 6ca46d0a053..c5b84080990 100644 --- a/packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentMethods.js +++ b/packages/api-plugin-fulfillment/src/resolvers/Query/fulfillmentMethods.js @@ -1,9 +1,8 @@ import getPaginatedResponseFromAggregate from "@reactioncommerce/api-utils/graphql/getPaginatedResponseFromAggregate.js"; import wasFieldRequested from "@reactioncommerce/api-utils/graphql/wasFieldRequested.js"; -import { decodeShopOpaqueId, decodeFulfillmentGroupOpaqueId } from "../../xforms/id.js"; /** - * @name Query/getFulfillmentMethods + * @name Query/fulfillmentMethods * @method * @memberof Fulfillment/Query * @summary Query for a list of fulfillment methods @@ -14,13 +13,10 @@ import { decodeShopOpaqueId, decodeFulfillmentGroupOpaqueId } from "../../xforms * @param {Object} info Info about the GraphQL request * @returns {Promise} Fulfillment methods */ -export default async function getFulfillmentMethods(_, args, context, info) { - const { shopId: opaqueShopId, fulfillmentTypeId: opaqueFulfillmentId, ...connectionArgs } = args; +export default async function fulfillmentMethods(_, args, context, info) { + const { shopId, fulfillmentTypeId, ...connectionArgs } = args; - const shopId = decodeShopOpaqueId(opaqueShopId); - const fulfillmentTypeId = decodeFulfillmentGroupOpaqueId(opaqueFulfillmentId); - - const { collection, pipeline } = await context.queries.getFulfillmentMethods(context, { + const { collection, pipeline } = await context.queries.fulfillmentMethods(context, { shopId, fulfillmentTypeId }); diff --git a/packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentType.js b/packages/api-plugin-fulfillment/src/resolvers/Query/fulfillmentType.js similarity index 52% rename from packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentType.js rename to packages/api-plugin-fulfillment/src/resolvers/Query/fulfillmentType.js index e1c9bd0f022..d259dc2d0f6 100644 --- a/packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentType.js +++ b/packages/api-plugin-fulfillment/src/resolvers/Query/fulfillmentType.js @@ -1,7 +1,5 @@ -import { decodeFulfillmentGroupOpaqueId, decodeShopOpaqueId } from "../../xforms/id.js"; - /** - * @name Query/getFulfillmentType + * @name Query/fulfillmentType * @method * @memberof Fulfillment/Query * @summary Query for a single fulfillment type @@ -12,16 +10,10 @@ import { decodeFulfillmentGroupOpaqueId, decodeShopOpaqueId } from "../../xforms * @param {Object} context - an object containing the per-request state * @returns {Promise} Fulfillment type */ -export default async function getFulfillmentType(_, args, context) { - const { - fulfillmentTypeId: opaqueTypeId, - shopId: opaqueShopId - } = args; - - const fulfillmentTypeId = decodeFulfillmentGroupOpaqueId(opaqueTypeId); - const shopId = decodeShopOpaqueId(opaqueShopId); +export default async function fulfillmentType(_, args, context) { + const { fulfillmentTypeId, shopId } = args; - return context.queries.getFulfillmentType(context, { + return context.queries.fulfillmentType(context, { fulfillmentTypeId, shopId }); diff --git a/packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentTypes.js b/packages/api-plugin-fulfillment/src/resolvers/Query/fulfillmentTypes.js similarity index 75% rename from packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentTypes.js rename to packages/api-plugin-fulfillment/src/resolvers/Query/fulfillmentTypes.js index 588f1c7532a..96ca79eb34e 100644 --- a/packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentTypes.js +++ b/packages/api-plugin-fulfillment/src/resolvers/Query/fulfillmentTypes.js @@ -1,6 +1,5 @@ import getPaginatedResponse from "@reactioncommerce/api-utils/graphql/getPaginatedResponse.js"; import wasFieldRequested from "@reactioncommerce/api-utils/graphql/wasFieldRequested.js"; -import { decodeShopOpaqueId } from "../../xforms/id.js"; /** * @name Query/FulfillmenTypes @@ -14,12 +13,10 @@ import { decodeShopOpaqueId } from "../../xforms/id.js"; * @param {Object} info Info about the GraphQL request * @returns {Promise} Fulfillment types under the Shop */ -export default async function getFulfillmentTypes(_, args, context, info) { - const { shopId: opaqueShopId, ...connectionArgs } = args; +export default async function fulfillmentTypes(_, args, context, info) { + const { shopId, ...connectionArgs } = args; - const shopId = decodeShopOpaqueId(opaqueShopId); - - const cursor = await context.queries.getFulfillmentTypes(context, { + const cursor = await context.queries.fulfillmentTypes(context, { shopId }); diff --git a/packages/api-plugin-fulfillment/src/resolvers/Query/index.js b/packages/api-plugin-fulfillment/src/resolvers/Query/index.js index fa4e54c3eda..aabd2daf563 100644 --- a/packages/api-plugin-fulfillment/src/resolvers/Query/index.js +++ b/packages/api-plugin-fulfillment/src/resolvers/Query/index.js @@ -1,9 +1,9 @@ -import getFulfillmentType from "./getFulfillmentType.js"; -import getFulfillmentTypes from "./getFulfillmentTypes.js"; -import getFulfillmentMethods from "./getFulfillmentMethods.js"; +import fulfillmentType from "./fulfillmentType.js"; +import fulfillmentTypes from "./fulfillmentTypes.js"; +import fulfillmentMethods from "./fulfillmentMethods.js"; export default { - getFulfillmentType, - getFulfillmentTypes, - getFulfillmentMethods + fulfillmentType, + fulfillmentTypes, + fulfillmentMethods }; diff --git a/packages/api-plugin-fulfillment/src/schemas/schema.graphql b/packages/api-plugin-fulfillment/src/schemas/schema.graphql index 5a7369daa09..9738d3d30d5 100644 --- a/packages/api-plugin-fulfillment/src/schemas/schema.graphql +++ b/packages/api-plugin-fulfillment/src/schemas/schema.graphql @@ -5,13 +5,13 @@ enum FulfillmentType { } "Default empty object for additionalData" -type emptyData { +type EmptyData { gqlType: String emptyData: Boolean } "Union of Additional Data fields" -union AdditionalData = emptyData +union AdditionalData = EmptyData """ A single fulfillment method. Fulfillment methods are shown to shoppers along with a quote for them, @@ -175,9 +175,6 @@ input updateFulfillmentTypeInputInfo { input updateFulfillmentTypeInput { "The shop to which this group belongs" groupInfo: updateFulfillmentTypeInputInfo! - - "An optional string identifying the mutation call, which will be returned in the response payload" - clientMutationId: String } "The updated group info from updateFulfillmentType" type updateFulfillmentTypeGroup { @@ -194,15 +191,12 @@ type updateFulfillmentTypeGroup { label: String! "Flag defining enabled/disabled status" - enabled: Boolean + enabled: Boolean! } "The response from the `updateFulfillmentType` mutation" type updateFulfillmentTypePayload { "The updated Group" group: updateFulfillmentTypeGroup! - - "The same string you sent with the mutation params, for matching mutation calls with their responses" - clientMutationId: String } @@ -258,16 +252,10 @@ input updateFulfillmentMethodInfo { input updateFulfillmentMethodInput { "Group Info Fulfillment Method" groupInfo: updateFulfillmentMethodInfo! - - "An optional string identifying the mutation call, which will be returned in the response payload" - clientMutationId: String } type updateFulfillmentMethodPayload { "The inserted Group" - group: FulfillmentMethodObj! - - "The same string you sent with the mutation params, for matching mutation calls with their responses" - clientMutationId: String + method: FulfillmentMethodObj! } extend type Mutation { @@ -286,7 +274,7 @@ extend type Mutation { extend type Query { "Get a fulfillment type" - getFulfillmentType( + fulfillmentType( "Fulfillment type id" fulfillmentTypeId: ID! @@ -295,7 +283,7 @@ extend type Query { ): FulfillmentTypeObj! "Get all fulfillment types" - getFulfillmentTypes( + fulfillmentTypes( "Shop ID" shopId: ID! @@ -316,7 +304,7 @@ extend type Query { ): FulfillmentTypeObjConnection! "Get all fulfillment methods for the given type" - getFulfillmentMethods( + fulfillmentMethods( "Shop ID" shopId: ID! diff --git a/packages/api-plugin-fulfillment/src/simpleSchemas.js b/packages/api-plugin-fulfillment/src/simpleSchemas.js index f204edc4c62..e1807b4da33 100644 --- a/packages/api-plugin-fulfillment/src/simpleSchemas.js +++ b/packages/api-plugin-fulfillment/src/simpleSchemas.js @@ -58,6 +58,45 @@ export const FulfillmentMethodSchema = new SimpleSchema({ "rate": Number }); +/** + * @name UserEditableFulfillmentMethodSchema + * @memberof Schemas + * @type {SimpleSchema} + * @summary Defines Schema for User editable fields of Fulfillment Method + * @property {Number} cost - optional cost + * @property {Number} handling - handling charges + * @property {Boolean} enabled - status of fulfillment method + * @property {String} label - displayed on the UI + * @property {String} displayMessageMethod - used to display additional message on UI specific to ff-method + * @property {Number} rate - rate for the method + */ +export const UserEditableFulfillmentMethodSchema = new SimpleSchema({ + cost: { + type: Number, + optional: true + }, + handling: { + type: Number, + optional: true + }, + enabled: { + type: Boolean, + optional: true + }, + label: { + type: String, + optional: true + }, + displayMessageMethod: { + type: String, + optional: true + }, + rate: { + type: Number, + optional: true + } +}); + /** * @name ProviderSchema * @memberof Schemas @@ -84,6 +123,8 @@ const ProviderSchema = new SimpleSchema({ * @property {String} fulfillmentType - fulfillmentType, not user editable * @property {String} displayMessageType - used to display additional message on UI specific to ff-type * @property {FulfillmentMethodSchema[]} methods - array of ff-methods under this ff-type + * @property {Date} createdAt required + * @property {Date} updatedAt required */ export const FulfillmentTypeSchema = new SimpleSchema({ "name": String, @@ -102,6 +143,12 @@ export const FulfillmentTypeSchema = new SimpleSchema({ }, "methods.$": { type: FulfillmentMethodSchema + }, + "createdAt": { + type: Date + }, + "updatedAt": { + type: Date } }); From 346460dc67e6cce11f494f24801e6e8788ab4d35 Mon Sep 17 00:00:00 2001 From: Sujith Date: Tue, 15 Nov 2022 11:15:57 +0530 Subject: [PATCH 47/71] fix: review comment fixes 2 Signed-off-by: Sujith --- .../src/mutations/createFulfillmentType.js | 2 +- .../mutations/createFulfillmentType.test.js | 2 +- .../src/mutations/updateFulfillmentType.js | 4 +- .../mutations/updateFulfillmentType.test.js | 2 +- .../queries/fulfillmentMethodsWithQuotes.js | 3 +- .../Mutation/updateFulfillmentMethod.js | 10 +- .../Mutation/updateFulfillmentType.js | 20 ++-- .../src/schemas/schema.graphql | 98 ++++++++----------- .../api-plugin-fulfillment/src/startup.js | 26 ++--- 9 files changed, 78 insertions(+), 89 deletions(-) diff --git a/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.js b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.js index 889c33ee6bf..9c5b8fe8665 100644 --- a/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.js +++ b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.js @@ -43,5 +43,5 @@ export default async function createFulfillmentType(context, input) { }); if (insertedCount === 0) throw new ReactionError("server-error", "Unable to create fulfillment type"); - return { group: { name: cleanedInput.name, fulfillmentType: cleanedInput.fulfillmentType } }; + return { fulfillmentType: { name: cleanedInput.name, fulfillmentType: cleanedInput.fulfillmentType } }; } diff --git a/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.test.js b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.test.js index 9ecef83c38b..4a3518f1de0 100644 --- a/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.test.js +++ b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.test.js @@ -62,7 +62,7 @@ test("add a new fulfillment type", async () => { const result = await createFulfillmentTypeMutation(mockContext, fulfillmentTypeInput); expect(result).toEqual({ - group: { + fulfillmentType: { name: "fulfillmentType123", fulfillmentType: "shipping" } diff --git a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.js b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.js index 256225265a4..649f201d042 100644 --- a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.js +++ b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.js @@ -27,7 +27,7 @@ const inputSchema = new SimpleSchema({ * @summary updates the selected fulfillment type * @param {Object} context - an object containing the per-request state * @param {Object} input - Input object - * @param {String} input.fulfillmentTypeId - fulfillment tpe id of group + * @param {String} input.fulfillmentTypeId - id of the fulfillment type * @param {String} input.shopId - Shop Id * @param {String} input.name - name of fulfillment type * @param {Boolean} input.enabled - status of ff-type @@ -66,5 +66,5 @@ export default async function updateFulfillmentType(context, input) { }); if (matchedCount === 0) throw new ReactionError("not-found", "Fulfillment type to update not found"); } - return { group: cleanedInput }; + return { fulfillmentType: cleanedInput }; } diff --git a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.test.js b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.test.js index 97442bd67f8..ad0cc58a31e 100644 --- a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.test.js +++ b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.test.js @@ -39,6 +39,6 @@ test("should update an existing fulfillment type", async () => { const result = await updateFulfillmentTypeMutation(mockContext, fulfillmentTypeInput); expect(result).toEqual({ - group: expectedOutput + fulfillmentType: expectedOutput }); }); diff --git a/packages/api-plugin-fulfillment/src/queries/fulfillmentMethodsWithQuotes.js b/packages/api-plugin-fulfillment/src/queries/fulfillmentMethodsWithQuotes.js index cb9065dcf3f..d45d6fd70e1 100644 --- a/packages/api-plugin-fulfillment/src/queries/fulfillmentMethodsWithQuotes.js +++ b/packages/api-plugin-fulfillment/src/queries/fulfillmentMethodsWithQuotes.js @@ -1,3 +1,4 @@ +import _ from "lodash"; import Logger from "@reactioncommerce/logger"; import ReactionError from "@reactioncommerce/reaction-error"; import extendCommonOrder from "../util/extendCommonOrder.js"; @@ -31,7 +32,7 @@ export default async function fulfillmentMethodsWithQuotes(commonOrder, context) const ffTypeFuncObjects = allFuncsArray.filter((func) => fulfillmentTypeInGroup === func.key); const funcs = ffTypeFuncObjects.map((func) => func.handler); - if (!funcs || !Array.isArray(funcs) || !funcs.length) throw new ReactionError("not-found", `No methods for Fulfillment type ${fulfillmentTypeInGroup}`); + if (_.isEmpty(funcs)) throw new ReactionError("not-found", `No methods for Fulfillment type ${fulfillmentTypeInGroup}`); let promises = funcs.map((rateFunction) => rateFunction(context, commonOrderExtended, [rates, retrialTargets])); await Promise.all(promises); diff --git a/packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentMethod.js b/packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentMethod.js index c292acf5758..5206414472f 100644 --- a/packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentMethod.js +++ b/packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentMethod.js @@ -6,15 +6,15 @@ import updateFulfillmentMethodMutation from "../../mutations/updateFulfillmentMe * @summary resolver for the updateFulfillmentMethod GraphQL mutation * @param {Object} parentResult - unused * @param {Object} args.input - an object of all mutation arguments that were sent by the client - * @param {String} args.input.fulfillmentTypeId - The fulfillment type to be updated - * @param {String} args.input.methodId - The fulfillment method to be updated - * @param {String} args.input.shopId - The ShopId to which the fulfillment group belongs - * @param {String} args.input.method - The fulfillment method data to be updated + * @param {String} args.input.fulfillmentMethodInfo.fulfillmentTypeId - The fulfillment type to be updated + * @param {String} args.input.fulfillmentMethodInfo.methodId - The fulfillment method to be updated + * @param {String} args.input.fulfillmentMethodInfo.shopId - The ShopId to which the fulfillment type belongs + * @param {String} args.input.fulfillmentMethodInfo.method - The fulfillment method data to be updated * @param {Object} context - an object containing the per-request state * @returns {Promise} updateFulfillmentMethodPayload */ export default async function updateFulfillmentMethod(parentResult, { input }, context) { - const { shopId, fulfillmentTypeId, methodId, method } = input.groupInfo; + const { shopId, fulfillmentTypeId, methodId, method } = input.fulfillmentMethodInfo; const { method: updatedMethod } = await updateFulfillmentMethodMutation(context, { shopId, diff --git a/packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentType.js b/packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentType.js index 45c3b72e813..5b068d1f316 100644 --- a/packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentType.js +++ b/packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentType.js @@ -6,24 +6,24 @@ import updateFulfillmentTypeMutation from "../../mutations/updateFulfillmentType * @summary resolver for the updateFulfillmentType GraphQL mutation * @param {Object} parentResult - unused * @param {Object} args.input - an object of all mutation arguments that were sent by the client - * @param {String} args.input.fulfillmentGroupId - The fulfillment group to be updated - * @param {String} args.input.shopId - The ShopId to which the fulfillment group belongs - * @param {String} args.input.name - The fulfillment group name to be updated - * @param {String} args.input.enabled - Flag to enable/disable group + * @param {String} args.input.fulfillmentTypeInfo.fulfillmentTypeId - The id of the fulfillment type to be updated + * @param {String} args.input.fulfillmentTypeInfo.shopId - The ShopId to which the fulfillment type belongs + * @param {String} args.input.fulfillmentTypeInfo.name - The name of fulfillment type to be updated + * @param {String} args.input.fulfillmentTypeInfo.enabled - Flag to enable/disable type * @param {Object} context - an object containing the per-request state * @returns {Promise} updateFulfillmentTypePayload */ export default async function updateFulfillmentType(parentResult, { input }, context) { - const { groupInfo } = input; - const { shopId, fulfillmentGroupId } = groupInfo; + const { fulfillmentTypeInfo } = input; + const { shopId, fulfillmentTypeId } = fulfillmentTypeInfo; - const { group } = await updateFulfillmentTypeMutation(context, { - ...groupInfo, + const { fulfillmentType } = await updateFulfillmentTypeMutation(context, { + ...fulfillmentTypeInfo, shopId, - fulfillmentGroupId + fulfillmentTypeId }); return { - group + fulfillmentType }; } diff --git a/packages/api-plugin-fulfillment/src/schemas/schema.graphql b/packages/api-plugin-fulfillment/src/schemas/schema.graphql index 9738d3d30d5..b55aec82ce7 100644 --- a/packages/api-plugin-fulfillment/src/schemas/schema.graphql +++ b/packages/api-plugin-fulfillment/src/schemas/schema.graphql @@ -87,7 +87,7 @@ type ProviderInfo{ label: String! "Flag defining enabled/disabled status" - enabled: Boolean + enabled: Boolean! } "Fulfillment type root object" type FulfillmentTypeObj implements Node { @@ -151,69 +151,60 @@ type CustomFulfillmentMethodObj implements Node { rate: Float! } -"Input needed to select a fulfillment option for a single fulfillment group on a cart" -input updateFulfillmentTypeInputInfo { - "The shop to which this group belongs" +"Input needed to update a fulfillment type" +input UpdateFulfillmentTypeInputInfo { + "The shop to which this fulfillment type belongs" shopId: ID! - "The group to update" - fulfillmentGroupId: ID! + "The fulfillment type to update" + fulfillmentTypeId: ID! "The type name to be updated" - name: String! + name: String "The label to be updated" - label: String! + label: String "The displayMessage to be updated" displayMessageType: String - "Flag to define if the group should be enabled/disabled" + "Flag to define if the type should be enabled/disabled" enabled: Boolean } -"Input needed to select a fulfillment option for a single fulfillment group on a cart" -input updateFulfillmentTypeInput { - "The shop to which this group belongs" - groupInfo: updateFulfillmentTypeInputInfo! +"Input needed to update a fulfillment type" +input UpdateFulfillmentTypeInput { + "The input info to update fulfillment type" + fulfillmentTypeInfo: UpdateFulfillmentTypeInputInfo! } -"The updated group info from updateFulfillmentType" -type updateFulfillmentTypeGroup { - "The shop to which this group belongs" +"The updated fulfillment type info from updateFulfillmentType" +type UpdateFulfillmentTypePayloadObj { + "The shop to which this fulfillment type belongs" shopId: ID! - "The group which was updated" - fulfillmentGroupId: ID! + "The fulfillment type which was updated" + fulfillmentTypeId: ID! - "The updated group name" + "The updated fulfillment type name" name: String! - "The updated group label" + "The updated fulfillment type label" label: String! "Flag defining enabled/disabled status" enabled: Boolean! } "The response from the `updateFulfillmentType` mutation" -type updateFulfillmentTypePayload { - "The updated Group" - group: updateFulfillmentTypeGroup! +type UpdateFulfillmentTypePayload { + "The updated fulfillment type" + fulfillmentType: UpdateFulfillmentTypePayloadObj! } -"Method info input" -input MethodInfoInput{ - "Name of the provider" - name: String! - +"UserEditable Method input" +input UserEditableMethodInput{ "Label of the provider" - label: String! - - "Fulfillmenttypes" - fulfillmentTypes: [String]! - - "Group" - group: String! + label: String "Display message Method" displayMessageMethod: String @@ -222,39 +213,36 @@ input MethodInfoInput{ cost: Int "Handling" - handling: Int! + handling: Int "Rate" - rate: Int! + rate: Int "Flag defining enabled/disabled status" - enabled: Boolean! - - "FulfillmentMethod" - fulfillmentMethod: String + enabled: Boolean } -"The updated group infofrom updateFulfillmentMethod" -input updateFulfillmentMethodInfo { +"The updated fulfillment method info from updateFulfillmentMethod" +input UpdateFulfillmentMethodInfo { "Shop Id" shopId: String! - "The group which was updated" + "The fulfillment type which has the method to be updated" fulfillmentTypeId: String! "The method which has to be updated" methodId: String! - "Method info" - method: MethodInfoInput + "Method input" + method: UserEditableMethodInput } -input updateFulfillmentMethodInput { - "Group Info Fulfillment Method" - groupInfo: updateFulfillmentMethodInfo! +input UpdateFulfillmentMethodInput { + "Info Fulfillment Method" + fulfillmentMethodInfo: UpdateFulfillmentMethodInfo! } -type updateFulfillmentMethodPayload { - "The inserted Group" +type UpdateFulfillmentMethodPayload { + "The inserted method" method: FulfillmentMethodObj! } @@ -262,14 +250,14 @@ extend type Mutation { "Updates the Name and Enabled fields for the provided Fulfillment Type" updateFulfillmentType( "Mutation input" - input: updateFulfillmentTypeInput! - ): updateFulfillmentTypePayload! + input: UpdateFulfillmentTypeInput! + ): UpdateFulfillmentTypePayload! "Updates the provided Fulfillment Method" updateFulfillmentMethod( "Mutation input" - input: updateFulfillmentMethodInput! - ): updateFulfillmentMethodPayload! + input: UpdateFulfillmentMethodInput! + ): UpdateFulfillmentMethodPayload! } extend type Query { diff --git a/packages/api-plugin-fulfillment/src/startup.js b/packages/api-plugin-fulfillment/src/startup.js index 6229e3bd58f..59a784386ac 100644 --- a/packages/api-plugin-fulfillment/src/startup.js +++ b/packages/api-plugin-fulfillment/src/startup.js @@ -12,18 +12,18 @@ export default async function fulfillmentTypeUndecidedStartup(context) { const shopId = shop._id; const undecidedRecord = await Fulfillment.findOne({ fulfillmentType: "undecided", shopId }); - if (!undecidedRecord) { - const groupInfo = { - name: "Undecided Group", - shopId, - provider: { - enabled: true, - label: "Undecided", - name: "undecided" - }, - fulfillmentType: "undecided" - }; - await context.mutations.createFulfillmentType(context.getInternalContext(), groupInfo); - } + + if (undecidedRecord) return; + const fulfillmentTypeInfo = { + name: "Undecided Type", + shopId, + provider: { + enabled: true, + label: "Undecided", + name: "undecided" + }, + fulfillmentType: "undecided" + }; + await context.mutations.createFulfillmentType(context.getInternalContext(), fulfillmentTypeInfo); }); } From da70978d5f566e855a9c59ee4028386b686421f3 Mon Sep 17 00:00:00 2001 From: Sujith Date: Tue, 15 Nov 2022 16:49:49 +0530 Subject: [PATCH 48/71] fix: review comment fix 3 Signed-off-by: Sujith --- .../src/mutations/createFlatRateFulfillmentMethod.js | 6 +++--- .../src/mutations/updateFlatRateFulfillmentMethod.js | 6 +++--- .../src/schemas/schema.graphql | 10 ++++++++-- .../src/simpleSchemas.js | 4 ++++ 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/createFlatRateFulfillmentMethod.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/createFlatRateFulfillmentMethod.js index 615b6c7cf45..283819dfa55 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/createFlatRateFulfillmentMethod.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/createFlatRateFulfillmentMethod.js @@ -29,9 +29,9 @@ export default async function createFlatRateFulfillmentMethod(context, input) { if (!shippingRecord) throw new ReactionError("server-error", "Unable to create fulfillment method without defined type"); method._id = Random.id(); - // MongoDB schema still uses `enabled` rather than `isEnabled` - method.enabled = method.isEnabled; - delete method.isEnabled; + // `isEnabled` has been marked @deprecated and will be removed in next release. 'enabled' is the replacement field + if (!method.enabled) method.enabled = method.isEnabled; // if user not yet using new field, continue to collect it from old field + if (method.isEnabled) delete method.isEnabled; // Hardcoded field, each ff-method plugin has to introduce this field for grouping purpose // Schema defined as optional=true for backward compatibility diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentMethod.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentMethod.js index 83f4c63618b..4105ff35911 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentMethod.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentMethod.js @@ -26,9 +26,9 @@ export default async function updateFlatRateFulfillmentMethod(context, input) { if (!methodId) throw new ReactionError("invalid-parameter", "Method ID to be updated not provided"); await context.validatePermissions(`reaction:legacy:fulfillmentMethods:${methodId}`, "update", { shopId }); - // MongoDB schema still uses `enabled` rather than `isEnabled` - method.enabled = method.isEnabled; - delete method.isEnabled; + // `isEnabled` has been marked @deprecated and will be removed in next release. 'enabled' is the replacement field + if (!method.enabled) method.enabled = method.isEnabled; // if user not yet using new field, continue to collect it from old field + if (method.isEnabled) delete method.isEnabled; // Hardcoded field, each ff-method plugin has to introduce this field for grouping purpose // Schema defined as optional=true for backward compatibility diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/schemas/schema.graphql b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/schemas/schema.graphql index 1ddbd2c72b8..fda1ed20187 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/schemas/schema.graphql +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/schemas/schema.graphql @@ -83,7 +83,10 @@ type FlatRateFulfillmentMethod implements Node { handling: Float! "Include this as a fulfillment option shown to shoppers during checkout?" - isEnabled: Boolean! + isEnabled: Boolean! @deprecated(reason: "Use `enabled`") + + "Replacement field for the deprecated `isEnabled`, matches with the database definition" + enabled: Boolean "The name of this method, for display in the user interface" label: String! @@ -122,7 +125,10 @@ input FlatRateFulfillmentMethodInput { handling: Float! "Include this as a fulfillment option shown to shoppers during checkout?" - isEnabled: Boolean! + isEnabled: Boolean! @deprecated(reason: "Use `enabled`") + + "Replacement field for the deprecated `isEnabled`, matches with the database definition" + enabled: Boolean "The name of this method, for display in the user interface" label: String! diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/simpleSchemas.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/simpleSchemas.js index 78dfab8931c..c705b49482e 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/simpleSchemas.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/simpleSchemas.js @@ -29,6 +29,10 @@ export const methodSchema = new SimpleSchema({ "group": String, "handling": Number, "isEnabled": Boolean, + "enabled": { + type: Boolean, + optional: true + }, "label": String, "name": String, "fulfillmentMethod": { From 5deec1c6b3289735ac99da693e1b53967fc805ba Mon Sep 17 00:00:00 2001 From: Sujith Date: Wed, 16 Nov 2022 19:54:58 +0530 Subject: [PATCH 49/71] fix: fix validatePermission Signed-off-by: Sujith --- .../src/mutations/updateFulfillmentMethod.js | 2 +- .../src/mutations/updateFulfillmentType.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.js b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.js index 67e78fa97cc..d7b0f887e0f 100644 --- a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.js +++ b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.js @@ -29,7 +29,7 @@ export default async function updateFulfillmentMethodMutation(context, input) { const { collections: { Fulfillment } } = context; const method = { ...inputMethod }; - await context.validatePermissions(`reaction:legacy:fulfillmentMethods:${methodId}`, "update", { shopId }); + await context.validatePermissions("reaction:legacy:fulfillmentMethods", "update", { shopId }); const ffTypeMethodRecord = await Fulfillment.findOne({ "_id": fulfillmentTypeId, diff --git a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.js b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.js index 649f201d042..08db8f03d75 100644 --- a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.js +++ b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.js @@ -42,7 +42,7 @@ export default async function updateFulfillmentType(context, input) { const { fulfillmentTypeId, shopId, name, enabled, label, displayMessageType } = cleanedInput; const { collections: { Fulfillment } } = context; - await context.validatePermissions(`reaction:legacy:fulfillmentTypes:${fulfillmentTypeId}`, "update", { shopId }); + await context.validatePermissions("reaction:legacy:fulfillmentTypes", "update", { shopId }); const updatedAt = new Date(); const providerObject = {}; From 7dfa566afe3e4df44a4a99f0c870eb25a2bc9495 Mon Sep 17 00:00:00 2001 From: Sujith Date: Wed, 16 Nov 2022 19:58:52 +0530 Subject: [PATCH 50/71] fix: fix validatePermissions Signed-off-by: Sujith --- .../src/mutations/deleteFlatRateFulfillmentMethod.js | 2 +- .../src/mutations/deleteFlatRateFulfillmentRestriction.js | 2 +- .../src/mutations/updateFlatRateFulfillmentMethod.js | 2 +- .../src/mutations/updateFlatRateFulfillmentRestriction.js | 2 +- .../src/queries/getFlatRateFulfillmentRestriction.js | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/deleteFlatRateFulfillmentMethod.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/deleteFlatRateFulfillmentMethod.js index 8d9ac9b7070..176039840ee 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/deleteFlatRateFulfillmentMethod.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/deleteFlatRateFulfillmentMethod.js @@ -19,7 +19,7 @@ export default async function deleteFlatRateFulfillmentMethod(context, input) { const { methodId, shopId } = input; const { collections: { Fulfillment } } = context; - await context.validatePermissions(`reaction:legacy:fulfillmentMethods:${methodId}`, "delete", { shopId }); + await context.validatePermissions("reaction:legacy:fulfillmentMethods", "delete", { shopId }); const shippingRecord = await Fulfillment.findOne({ "methods._id": methodId, diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/deleteFlatRateFulfillmentRestriction.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/deleteFlatRateFulfillmentRestriction.js index 422176806d2..4b42cd84308 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/deleteFlatRateFulfillmentRestriction.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/deleteFlatRateFulfillmentRestriction.js @@ -19,7 +19,7 @@ export default async function deleteFlatRateFulfillmentRestriction(context, inpu const { restrictionId, shopId } = input; const { collections: { FulfillmentRestrictions } } = context; - await context.validatePermissions(`reaction:legacy:fulfillmentRestrictions:${restrictionId}`, "delete", { shopId }); + await context.validatePermissions("reaction:legacy:fulfillmentRestrictions", "delete", { shopId }); const { ok, value } = await FulfillmentRestrictions.findOneAndDelete({ _id: restrictionId, diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentMethod.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentMethod.js index 4105ff35911..9857f497dd1 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentMethod.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentMethod.js @@ -24,7 +24,7 @@ export default async function updateFlatRateFulfillmentMethod(context, input) { const method = { ...inputMethod }; if (!methodId) throw new ReactionError("invalid-parameter", "Method ID to be updated not provided"); - await context.validatePermissions(`reaction:legacy:fulfillmentMethods:${methodId}`, "update", { shopId }); + await context.validatePermissions("reaction:legacy:fulfillmentMethods", "update", { shopId }); // `isEnabled` has been marked @deprecated and will be removed in next release. 'enabled' is the replacement field if (!method.enabled) method.enabled = method.isEnabled; // if user not yet using new field, continue to collect it from old field diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentRestriction.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentRestriction.js index c7378bdb000..40c553eddbf 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentRestriction.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentRestriction.js @@ -23,7 +23,7 @@ export default async function updateFlatRateFulfillmentRestriction(context, inpu const { restriction, restrictionId, shopId } = cleanedInput; const { collections: { FulfillmentRestrictions } } = context; - await context.validatePermissions(`reaction:legacy:fulfillmentRestrictions:${restrictionId}`, "update", { shopId }); + await context.validatePermissions("reaction:legacy:fulfillmentRestrictions", "update", { shopId }); const { matchedCount } = await FulfillmentRestrictions.updateOne({ _id: restrictionId, diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/getFlatRateFulfillmentRestriction.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/getFlatRateFulfillmentRestriction.js index 71d0d10cb27..a558fd67266 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/getFlatRateFulfillmentRestriction.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/getFlatRateFulfillmentRestriction.js @@ -11,7 +11,7 @@ export default async function getFlatRateFulfillmentRestriction(context, { restrictionId, shopId } = {}) { const { collections: { FulfillmentRestrictions } } = context; - await context.validatePermissions(`reaction:legacy:fulfillmentRestrictions:${restrictionId}`, "read", { shopId }); + await context.validatePermissions("reaction:legacy:fulfillmentRestrictions", "read", { shopId }); return FulfillmentRestrictions.findOne({ _id: restrictionId, From 6e1c711441442a7e4abee005a7d048936de3d035 Mon Sep 17 00:00:00 2001 From: Sujith Date: Thu, 17 Nov 2022 13:17:24 +0530 Subject: [PATCH 51/71] fix: review comment fixes Signed-off-by: Sujith --- .../api-plugin-fulfillment-method-pickup-store/README.md | 2 +- ...tore.js => fulfillmentMethodsWithQuotesPickupStore.js} | 6 +++--- ...js => fulfillmentMethodsWithQuotesPickupStore.test.js} | 8 ++++---- .../src/index.js | 4 ++-- .../src/schemas/schema.graphql | 8 ++++---- .../checkAndCreateFulfillmentMethod.test.js.snap | 3 --- .../src/util/checkAndCreateFulfillmentMethod.test.js | 5 ++--- .../src/util/validateOrderMethodsstore.js | 2 +- 8 files changed, 17 insertions(+), 21 deletions(-) rename packages/api-plugin-fulfillment-method-pickup-store/src/{getFulfillmentMethodsWithQuotesPickupStore.js => fulfillmentMethodsWithQuotesPickupStore.js} (90%) rename packages/api-plugin-fulfillment-method-pickup-store/src/{getFulfillmentMethodsWithQuotesPickupStore.test.js => fulfillmentMethodsWithQuotesPickupStore.test.js} (86%) delete mode 100644 packages/api-plugin-fulfillment-method-pickup-store/src/util/__snapshots__/checkAndCreateFulfillmentMethod.test.js.snap diff --git a/packages/api-plugin-fulfillment-method-pickup-store/README.md b/packages/api-plugin-fulfillment-method-pickup-store/README.md index 3fa11ee62fa..9ccd75f9b54 100644 --- a/packages/api-plugin-fulfillment-method-pickup-store/README.md +++ b/packages/api-plugin-fulfillment-method-pickup-store/README.md @@ -3,7 +3,7 @@ This is a fulfillment-method plugin which which works along with the base `api-plugin-fulfillment` and the ff-type plugin `api-plugin-fulfillment-type-pickup`. This plugin actually implements all the functionality associated with this fulfillment-method. It can work along-side other fulfillment-methods under the same ff-type. This main features/functionalities of this plugin includes the following: -* getFulfillmentMethodsWithQuotesPickupStore - returns the quote or equivalent details for the method when called from base ff plugin +* fulfillmentMethodsWithQuotesPickupStore - returns the quote or equivalent details for the method when called from base ff plugin * preStartup - extends the union of "methodAdditionalData" with data structure specific to Store * startup - Inserts the required ff-method entry into Fulfillment collection * util/checkAndCreateFulfillmentMethod - confirms existing ff-type entry and adds a new ff-method under it. diff --git a/packages/api-plugin-fulfillment-method-pickup-store/src/getFulfillmentMethodsWithQuotesPickupStore.js b/packages/api-plugin-fulfillment-method-pickup-store/src/fulfillmentMethodsWithQuotesPickupStore.js similarity index 90% rename from packages/api-plugin-fulfillment-method-pickup-store/src/getFulfillmentMethodsWithQuotesPickupStore.js rename to packages/api-plugin-fulfillment-method-pickup-store/src/fulfillmentMethodsWithQuotesPickupStore.js index a43f8693704..08ebe85cfc9 100644 --- a/packages/api-plugin-fulfillment-method-pickup-store/src/getFulfillmentMethodsWithQuotesPickupStore.js +++ b/packages/api-plugin-fulfillment-method-pickup-store/src/fulfillmentMethodsWithQuotesPickupStore.js @@ -4,7 +4,7 @@ import collectStoreDetails from "./util/collectStoreDetails.js"; const packageName = "fulfillment-method-pickup-store"; const fulfillmentTypeName = "pickup"; const fulfillmentMethodName = "store"; -const logCtx = { name: "fulfillment-method-pickup-store", file: "getFulfillmentMethodsWithQuotesPickupStore" }; +const logCtx = { name: "fulfillment-method-pickup-store", file: "fulfillmentMethodsWithQuotesPickupStore" }; /** * @summary Returns a list of fulfillment method quotes based on the items in a fulfillment group. @@ -18,7 +18,7 @@ const logCtx = { name: "fulfillment-method-pickup-store", file: "getFulfillmentM * shipping rates. * @private */ -export default async function getFulfillmentMethodsWithQuotesPickupStore(context, commonOrder, previousQueryResults = []) { +export default async function fulfillmentMethodsWithQuotesPickupStore(context, commonOrder, previousQueryResults = []) { const { collections: { Fulfillment } } = context; const [rates = [], retrialTargets = []] = previousQueryResults; const currentMethodInfo = { packageName }; @@ -70,6 +70,6 @@ export default async function getFulfillmentMethodsWithQuotesPickupStore(context return [rates, retrialTargets]; } - Logger.debug({ ...logCtx, rates }, "Store getFulfillmentMethodsWithQuotesPickupStore"); + Logger.debug({ ...logCtx, rates }, "Store fulfillmentMethodsWithQuotesPickupStore"); return [rates, retrialTargets]; } diff --git a/packages/api-plugin-fulfillment-method-pickup-store/src/getFulfillmentMethodsWithQuotesPickupStore.test.js b/packages/api-plugin-fulfillment-method-pickup-store/src/fulfillmentMethodsWithQuotesPickupStore.test.js similarity index 86% rename from packages/api-plugin-fulfillment-method-pickup-store/src/getFulfillmentMethodsWithQuotesPickupStore.test.js rename to packages/api-plugin-fulfillment-method-pickup-store/src/fulfillmentMethodsWithQuotesPickupStore.test.js index a9c378d8bfb..ac9a23d8a16 100644 --- a/packages/api-plugin-fulfillment-method-pickup-store/src/getFulfillmentMethodsWithQuotesPickupStore.test.js +++ b/packages/api-plugin-fulfillment-method-pickup-store/src/fulfillmentMethodsWithQuotesPickupStore.test.js @@ -1,5 +1,5 @@ import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; -import getFulfillmentMethodsWithQuotesPickupStore from "./getFulfillmentMethodsWithQuotesPickupStore.js"; +import fulfillmentMethodsWithQuotesPickupStore from "./fulfillmentMethodsWithQuotesPickupStore.js"; test("should return previousResults if pickup is not among FailedRequests", async () => { const commonOrder = { @@ -12,7 +12,7 @@ test("should return previousResults if pickup is not among FailedRequests", asyn } ] ]; - const result = await getFulfillmentMethodsWithQuotesPickupStore(mockContext, commonOrder, previousResults); + const result = await fulfillmentMethodsWithQuotesPickupStore(mockContext, commonOrder, previousResults); expect(result).toEqual(previousResults); }); @@ -36,7 +36,7 @@ test("should return previousResults if not fulfillment records enabled", async ( mockContext.collections.Fulfillment = { find: jest.fn(() => ({ toArray: () => [] })) }; - const result = await getFulfillmentMethodsWithQuotesPickupStore(mockContext, commonOrder, previousResults); + const result = await fulfillmentMethodsWithQuotesPickupStore(mockContext, commonOrder, previousResults); expect(result).toEqual(previousResults); }); @@ -123,6 +123,6 @@ test("should return rates witout error", async () => { mockContext.collections.Fulfillment = { find: jest.fn(() => ({ toArray: () => [pickupDoc] })) }; - const result = await getFulfillmentMethodsWithQuotesPickupStore(mockContext, commonOrder, previousResults); + const result = await fulfillmentMethodsWithQuotesPickupStore(mockContext, commonOrder, previousResults); expect(result).toEqual(expectedResult); }); diff --git a/packages/api-plugin-fulfillment-method-pickup-store/src/index.js b/packages/api-plugin-fulfillment-method-pickup-store/src/index.js index f26c434e73f..05bb2603785 100644 --- a/packages/api-plugin-fulfillment-method-pickup-store/src/index.js +++ b/packages/api-plugin-fulfillment-method-pickup-store/src/index.js @@ -3,7 +3,7 @@ import fulfillmentMethodPickupStorePreStartup from "./preStartup.js"; import fulfillmentMethodPickupStoreStartup from "./startup.js"; import { MethodStoreData } from "./simpleSchemas.js"; import schemas from "./schemas/index.js"; -import getFulfillmentMethodsWithQuotesPickupStore from "./getFulfillmentMethodsWithQuotesPickupStore.js"; +import fulfillmentMethodsWithQuotesPickupStore from "./fulfillmentMethodsWithQuotesPickupStore.js"; import validateOrderMethodsStore from "./util/validateOrderMethodsStore.js"; const require = createRequire(import.meta.url); @@ -29,7 +29,7 @@ export default async function register(app) { preStartup: [fulfillmentMethodPickupStorePreStartup], startup: [fulfillmentMethodPickupStoreStartup], validateOrderMethods: [{ key: "store", handler: validateOrderMethodsStore }], - getFulfillmentMethodsWithQuotes: [{ key: "pickup", handler: getFulfillmentMethodsWithQuotesPickupStore }] + fulfillmentMethodsWithQuotes: [{ key: "pickup", handler: fulfillmentMethodsWithQuotesPickupStore }] } }); } diff --git a/packages/api-plugin-fulfillment-method-pickup-store/src/schemas/schema.graphql b/packages/api-plugin-fulfillment-method-pickup-store/src/schemas/schema.graphql index fccb208a807..7eea8eac63b 100644 --- a/packages/api-plugin-fulfillment-method-pickup-store/src/schemas/schema.graphql +++ b/packages/api-plugin-fulfillment-method-pickup-store/src/schemas/schema.graphql @@ -1,14 +1,14 @@ "Additional data fields from Store" -type storeFields { +type StoreFields { storeId: String storeAddress: String storeTiming: String } "Additional Store data - Pickup" -type storeData { +type StoreData { gqlType: String - storeData: [storeFields] + storeData: [StoreFields] } -extend union AdditionalData = storeData +extend union AdditionalData = StoreData diff --git a/packages/api-plugin-fulfillment-method-pickup-store/src/util/__snapshots__/checkAndCreateFulfillmentMethod.test.js.snap b/packages/api-plugin-fulfillment-method-pickup-store/src/util/__snapshots__/checkAndCreateFulfillmentMethod.test.js.snap deleted file mode 100644 index e08c70088fd..00000000000 --- a/packages/api-plugin-fulfillment-method-pickup-store/src/util/__snapshots__/checkAndCreateFulfillmentMethod.test.js.snap +++ /dev/null @@ -1,3 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should throw error and NOT call createFulfillmentMethod mutation 1`] = `"Unable to create fulfillment method Pickup-Store without defined type"`; diff --git a/packages/api-plugin-fulfillment-method-pickup-store/src/util/checkAndCreateFulfillmentMethod.test.js b/packages/api-plugin-fulfillment-method-pickup-store/src/util/checkAndCreateFulfillmentMethod.test.js index 9bb9869eff8..7b817c13be4 100644 --- a/packages/api-plugin-fulfillment-method-pickup-store/src/util/checkAndCreateFulfillmentMethod.test.js +++ b/packages/api-plugin-fulfillment-method-pickup-store/src/util/checkAndCreateFulfillmentMethod.test.js @@ -83,7 +83,6 @@ test("should throw error and NOT call createFulfillmentMethod mutation", async ( }; mockContext.mutations.createFulfillmentMethod = jest.fn().mockName("createFulfillmentMethod").mockReturnValueOnce(Promise.resolve(method)); - await expect(checkAndCreateFulfillmentMethod(mockContext, { shopId, fulfillmentTypeId, method })).rejects.toThrowErrorMatchingSnapshot(); - // await checkAndCreateFulfillmentMethod(mockContext, { shopId, fulfillmentTypeId, method }); - // expect(mockContext.mutations.createFulfillmentMethod).toHaveBeenCalled(); + const expectedError = "Unable to create fulfillment method Pickup-Store without defined type"; + await expect(checkAndCreateFulfillmentMethod(mockContext, { shopId, fulfillmentTypeId, method })).rejects.toThrowError(expectedError); }); diff --git a/packages/api-plugin-fulfillment-method-pickup-store/src/util/validateOrderMethodsstore.js b/packages/api-plugin-fulfillment-method-pickup-store/src/util/validateOrderMethodsstore.js index 91e985d2716..9f7b08c7f3b 100644 --- a/packages/api-plugin-fulfillment-method-pickup-store/src/util/validateOrderMethodsstore.js +++ b/packages/api-plugin-fulfillment-method-pickup-store/src/util/validateOrderMethodsstore.js @@ -5,7 +5,7 @@ * @param {Object[]} validationResults - Validation results collected till now * @returns {Object[]} validationResults - with the validation details populated */ -export default function validateOrderMethodsstore(context, commonOrder, validationResults = []) { +export default function validateOrderMethodsStore(context, commonOrder, validationResults = []) { // This is a dummy code to demo how validation results could be returned. // Commenting out since the placeOrder will fail if the error record is pushed. From ddca358ac1977a73b075c0e8dc6ea0187e6bfe5d Mon Sep 17 00:00:00 2001 From: Sujith Date: Thu, 17 Nov 2022 14:46:17 +0530 Subject: [PATCH 52/71] fix: review comment fixes Signed-off-by: Sujith --- .../README.md | 2 +- ...ntMethodsWithQuotesShippingDynamicRate.js} | 3 +-- ...hodsWithQuotesShippingDynamicRate.test.js} | 8 +++--- .../src/index.js | 4 +-- ...eckAndCreateFulfillmentMethod.test.js.snap | 3 --- .../checkAndCreateFulfillmentMethod.test.js | 3 ++- .../README.md | 8 +++--- ...lmentMethodsWithQuotesShippingFlatRate.js} | 4 +-- .../src/index.js | 4 +-- .../createFlatRateFulfillmentMethod.js | 13 +++++++++- .../createFlatRateFulfillmentRestriction.js | 13 +++++++++- .../deleteFlatRateFulfillmentMethod.js | 4 ++- .../deleteFlatRateFulfillmentRestriction.js | 4 ++- .../updateFlatRateFulfillmentMethod.js | 14 +++++++++- .../updateFlatRateFulfillmentRestriction.js | 14 +++++++++- .../queries/flatRateFulfillmentRestriction.js | 21 +++++++++++++++ .../flatRateFulfillmentRestrictions.js | 19 ++++++++++++++ .../getFlatRateFulfillmentRestriction.js | 13 ++++------ .../getFlatRateFulfillmentRestrictions.js | 11 +++----- .../Query/flatRateFulfillmentRestriction.js | 16 ++++++++++++ .../Query/flatRateFulfillmentRestrictions.js | 26 +++++++++++++++++++ .../getFlatRateFulfillmentRestriction.js | 1 + .../getFlatRateFulfillmentRestrictions.js | 1 + 23 files changed, 168 insertions(+), 41 deletions(-) rename packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/{getFulfillmentMethodsWithQuotesShippingDynamicRate.js => fulfillmentMethodsWithQuotesShippingDynamicRate.js} (94%) rename packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/{getFulfillmentMethodsWithQuotesShippingDynamicRate.test.js => fulfillmentMethodsWithQuotesShippingDynamicRate.test.js} (84%) delete mode 100644 packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/util/__snapshots__/checkAndCreateFulfillmentMethod.test.js.snap rename packages/api-plugin-fulfillment-method-shipping-flat-rate/src/{getFulfillmentMethodsWithQuotesShippingFlatRate.js => fulfillmentMethodsWithQuotesShippingFlatRate.js} (95%) create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/flatRateFulfillmentRestriction.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/flatRateFulfillmentRestrictions.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Query/flatRateFulfillmentRestriction.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Query/flatRateFulfillmentRestrictions.js diff --git a/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/README.md b/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/README.md index 09b74e8d31f..d2a5a848bf6 100644 --- a/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/README.md +++ b/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/README.md @@ -3,7 +3,7 @@ This is a fulfillment-method plugin which which works along with the base `api-plugin-fulfillment` and the ff-type plugin `api-plugin-fulfillment-type-shipping`. This plugin actually implements all the functionality associated with this fulfillment-method. It can work along-side other fulfillment-methods under the same ff-type. This main features/functionalities of this plugin includes the following: -* getFulfillmentMethodsWithQuotesShippingDynamicRate - returns the quote or equivalent details for the method when called from base ff plugin +* fulfillmentMethodsWithQuotesShippingDynamicRate - returns the quote or equivalent details for the method when called from base ff plugin * preStartup - extends the union of "methodAdditionalData" with data structure specific to Dynamic Rate * startup - Inserts the required ff-method entry into Fulfillment collection * util/checkAndCreateFulfillmentMethod - confirms existing ff-type entry and adds a new ff-method under it. diff --git a/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/getFulfillmentMethodsWithQuotesShippingDynamicRate.js b/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/fulfillmentMethodsWithQuotesShippingDynamicRate.js similarity index 94% rename from packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/getFulfillmentMethodsWithQuotesShippingDynamicRate.js rename to packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/fulfillmentMethodsWithQuotesShippingDynamicRate.js index cb768b106f0..96feabb908d 100644 --- a/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/getFulfillmentMethodsWithQuotesShippingDynamicRate.js +++ b/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/fulfillmentMethodsWithQuotesShippingDynamicRate.js @@ -10,14 +10,13 @@ const fulfillmentMethodName = "dynamicRate"; * @param {Object} commonOrder - details about the purchase a user wants to make. * @param {Array} [previousQueryResults] - an array of shipping rates and * info about failed calls to the APIs of some shipping methods providers - * e.g Shippo. * @returns {Array} - an array that contains two arrays: the first array will * be an updated list of shipping rates, and the second will contain info for * retrying this specific package if any errors occurred while retrieving the * shipping rates. * @private */ -export default async function getFulfillmentMethodsWithQuotesShippingDynamicRate(context, commonOrder, previousQueryResults = []) { +export default async function fulfillmentMethodsWithQuotesShippingDynamicRate(context, commonOrder, previousQueryResults = []) { const { collections: { Fulfillment } } = context; const [rates = [], retrialTargets = []] = previousQueryResults; const currentMethodInfo = { packageName }; diff --git a/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/getFulfillmentMethodsWithQuotesShippingDynamicRate.test.js b/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/fulfillmentMethodsWithQuotesShippingDynamicRate.test.js similarity index 84% rename from packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/getFulfillmentMethodsWithQuotesShippingDynamicRate.test.js rename to packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/fulfillmentMethodsWithQuotesShippingDynamicRate.test.js index 79e174db36b..9a8add266e8 100644 --- a/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/getFulfillmentMethodsWithQuotesShippingDynamicRate.test.js +++ b/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/fulfillmentMethodsWithQuotesShippingDynamicRate.test.js @@ -1,5 +1,5 @@ import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; -import getFulfillmentMethodsWithQuotesShippingDynamicRate from "./getFulfillmentMethodsWithQuotesShippingDynamicRate.js"; +import fulfillmentMethodsWithQuotesShippingDynamicRate from "./fulfillmentMethodsWithQuotesShippingDynamicRate.js"; test("should return previousResults if Shipping is not among FailedRequests", async () => { const commonOrder = { @@ -12,7 +12,7 @@ test("should return previousResults if Shipping is not among FailedRequests", as } ] ]; - const result = await getFulfillmentMethodsWithQuotesShippingDynamicRate(mockContext, commonOrder, previousResults); + const result = await fulfillmentMethodsWithQuotesShippingDynamicRate(mockContext, commonOrder, previousResults); expect(result).toEqual(previousResults); }); @@ -36,7 +36,7 @@ test("should return previousResults if not fulfillment records enabled", async ( mockContext.collections.Fulfillment = { find: jest.fn(() => ({ toArray: () => [] })) }; - const result = await getFulfillmentMethodsWithQuotesShippingDynamicRate(mockContext, commonOrder, previousResults); + const result = await fulfillmentMethodsWithQuotesShippingDynamicRate(mockContext, commonOrder, previousResults); expect(result).toEqual(previousResults); }); @@ -112,6 +112,6 @@ test("should return rates witout error", async () => { mockContext.collections.Fulfillment = { find: jest.fn(() => ({ toArray: () => [shippingDoc] })) }; - const result = await getFulfillmentMethodsWithQuotesShippingDynamicRate(mockContext, commonOrder, previousResults); + const result = await fulfillmentMethodsWithQuotesShippingDynamicRate(mockContext, commonOrder, previousResults); expect(result).toEqual(expectedResult); }); diff --git a/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/index.js b/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/index.js index b4848d00912..fefa6dddd74 100644 --- a/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/index.js +++ b/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/index.js @@ -3,7 +3,7 @@ import { MethodDynamicRateData } from "./simpleSchemas.js"; import preStartup from "./preStartup.js"; import startup from "./startup.js"; import schemas from "./schemas/index.js"; -import getFulfillmentMethodsWithQuotesShippingDynamicRate from "./getFulfillmentMethodsWithQuotesShippingDynamicRate.js"; +import fulfillmentMethodsWithQuotesShippingDynamicRate from "./fulfillmentMethodsWithQuotesShippingDynamicRate.js"; import validateOrderMethodsDynamicRate from "./util/validateOrderMethodsDynamicRate.js"; const require = createRequire(import.meta.url); @@ -29,7 +29,7 @@ export default async function register(app) { preStartup: [preStartup], startup: [startup], validateOrderMethods: [{ key: "dynamicRate", handler: validateOrderMethodsDynamicRate }], - getFulfillmentMethodsWithQuotes: [{ key: "shipping", handler: getFulfillmentMethodsWithQuotesShippingDynamicRate }] + fulfillmentMethodsWithQuotes: [{ key: "shipping", handler: fulfillmentMethodsWithQuotesShippingDynamicRate }] } }); } diff --git a/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/util/__snapshots__/checkAndCreateFulfillmentMethod.test.js.snap b/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/util/__snapshots__/checkAndCreateFulfillmentMethod.test.js.snap deleted file mode 100644 index 7205e12df65..00000000000 --- a/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/util/__snapshots__/checkAndCreateFulfillmentMethod.test.js.snap +++ /dev/null @@ -1,3 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should throw error and NOT call createFulfillmentMethod mutation 1`] = `"Unable to create fulfillment method Shipping-DynamicRate without defined type"`; diff --git a/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/util/checkAndCreateFulfillmentMethod.test.js b/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/util/checkAndCreateFulfillmentMethod.test.js index e6faaf0c17f..7842c77810c 100644 --- a/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/util/checkAndCreateFulfillmentMethod.test.js +++ b/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/util/checkAndCreateFulfillmentMethod.test.js @@ -83,5 +83,6 @@ test("should throw error and NOT call createFulfillmentMethod mutation", async ( }; mockContext.mutations.createFulfillmentMethod = jest.fn().mockName("createFulfillmentMethod").mockReturnValueOnce(Promise.resolve(method)); - await expect(checkAndCreateFulfillmentMethod(mockContext, { shopId, fulfillmentTypeId, method })).rejects.toThrowErrorMatchingSnapshot(); + const expectedError = "Unable to create fulfillment method Shipping-DynamicRate without defined type"; + await expect(checkAndCreateFulfillmentMethod(mockContext, { shopId, fulfillmentTypeId, method })).rejects.toThrowError(expectedError); }); diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/README.md b/packages/api-plugin-fulfillment-method-shipping-flat-rate/README.md index 7bec8e1ed84..ee77d383504 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/README.md +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/README.md @@ -3,12 +3,14 @@ This is a fulfillment-method plugin which which works along with the base `api-plugin-fulfillment` and the ff-type plugin `api-plugin-fulfillment-type-shipping`. This plugin actually implements all the functionality associated with this fulfillment-method. It can work along-side other fulfillment-methods under the same ff-type. This main features/functionalities of this plugin includes the following: -* getFulfillmentMethodsWithQuotesShippingFlatRate - returns the quote or equivalent details for the method when called from base ff plugin +* fulfillmentMethodsWithQuotesShippingFlatRate - returns the quote or equivalent details for the method when called from base ff plugin * preStartup - extends the union of "methodAdditionalData" with data structure specific to FlatRate (dummy and can be changed) * create/update/delete FlatRateFulfillmentMethods * create/update/delete FlatRateFulfillmentRestrictions -* getFlatRateFulfillmentMethod(s) -* getFlatRateFulfillmentRestriction(s) +* getFlatRateFulfillmentMethod(s) - deprecated +* getFlatRateFulfillmentRestriction(s) - deprecated +* flatRateFulfillmentMethod(s) +* flatRateFulfillmentRestriction(s) ## Developer Certificate of Origin We use the [Developer Certificate of Origin (DCO)](https://developercertificate.org/) in lieu of a Contributor License Agreement for all contributions to Reaction Commerce open source projects. We request that contributors agree to the terms of the DCO and indicate that agreement by signing all commits made to Reaction Commerce projects by adding a line with your name and email address to every Git commit message contributed: diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/getFulfillmentMethodsWithQuotesShippingFlatRate.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/fulfillmentMethodsWithQuotesShippingFlatRate.js similarity index 95% rename from packages/api-plugin-fulfillment-method-shipping-flat-rate/src/getFulfillmentMethodsWithQuotesShippingFlatRate.js rename to packages/api-plugin-fulfillment-method-shipping-flat-rate/src/fulfillmentMethodsWithQuotesShippingFlatRate.js index e48c82ae482..2685a982bb5 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/getFulfillmentMethodsWithQuotesShippingFlatRate.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/fulfillmentMethodsWithQuotesShippingFlatRate.js @@ -19,12 +19,12 @@ const fulfillmentMethodName = "flatRate"; * shipping rates. * @private */ -export default async function getFulfillmentMethodsWithQuotesShippingFlatRate(context, commonOrder, previousQueryResults = []) { +export default async function fulfillmentMethodsWithQuotesShippingFlatRate(context, commonOrder, previousQueryResults = []) { const { collections: { Fulfillment } } = context; const [rates = [], retrialTargets = []] = previousQueryResults; const currentMethodInfo = { packageName }; - logCtx.file = "src/getFulfillmentMethodsWithQuotesShippingFlatRate.js"; + logCtx.file = "src/fulfillmentMethodsWithQuotesShippingFlatRate.js"; if (retrialTargets.length > 0) { const isNotAmongFailedRequests = retrialTargets.every((target) => target.packageName !== packageName); diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/index.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/index.js index 05f4c06ce70..f30ab088de9 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/index.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/index.js @@ -1,5 +1,5 @@ import { createRequire } from "module"; -import getFulfillmentMethodsWithQuotesShippingFlatRate from "./getFulfillmentMethodsWithQuotesShippingFlatRate.js"; +import fulfillmentMethodsWithQuotesShippingFlatRate from "./fulfillmentMethodsWithQuotesShippingFlatRate.js"; import validateOrderMethodsFlatRate from "./util/validateOrderMethodsFlatRate.js"; import resolvers from "./resolvers/index.js"; import mutations from "./mutations/index.js"; @@ -42,7 +42,7 @@ export default async function register(app) { functionsByType: { preStartup: [fulfillmentMethodShippingFlatRatePreStartup], validateOrderMethods: [{ key: "flatRate", handler: validateOrderMethodsFlatRate }], - getFulfillmentMethodsWithQuotes: [{ key: "shipping", handler: getFulfillmentMethodsWithQuotesShippingFlatRate }] + fulfillmentMethodsWithQuotes: [{ key: "shipping", handler: fulfillmentMethodsWithQuotesShippingFlatRate }] }, shopSettingsConfig: { isShippingRatesFulfillmentEnabled: { diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/createFlatRateFulfillmentMethod.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/createFlatRateFulfillmentMethod.js index 283819dfa55..1f35dcdc095 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/createFlatRateFulfillmentMethod.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/createFlatRateFulfillmentMethod.js @@ -12,7 +12,18 @@ const inputSchema = new SimpleSchema({ * @method createFlatRateFulfillmentMethod * @summary Creates a flat rate fulfillment method * @param {Object} context - an object containing the per-request state - * @param {Object} input - Input (see SimpleSchema) + * @param {Object} input - Input object + * @param {String} input.shopId - Shop Id + * @param {Number} input.method.cost - Cost + * @param {String} input.method.group - Group name (free / ground) + * @param {Number} input.method.handling - Handling price + * @param {Boolean} input.method.isEnabled - Status + * @param {Boolean} input.method.enabled - Status + * @param {String} input.method.label - Display label of method + * @param {String} input.method.name - Name of method + * @param {String} input.method.fulfillmentMethod - Fulfullment method name non-editable + * @param {String[]} input.method.fulfillmentTypes - Array of Fulfullment type + * @param {Number} input.method.rate - Rate of method * @returns {Promise} An object with a `method` property containing the created method */ export default async function createFlatRateFulfillmentMethod(context, input) { diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/createFlatRateFulfillmentRestriction.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/createFlatRateFulfillmentRestriction.js index 7d62f643bcf..82e538f0752 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/createFlatRateFulfillmentRestriction.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/createFlatRateFulfillmentRestriction.js @@ -12,7 +12,18 @@ const inputSchema = new SimpleSchema({ * @method createFlatRateFulfillmentRestriction * @summary Creates a flat rate fulfillment restriction * @param {Object} context - an object containing the per-request state - * @param {Object} input - Input (see SimpleSchema) + * @param {Object} input - Input + * @param {String} input.shopId - Shop Id + * @param {String[]} input.restriction.methodIds - Array of methodIds + * @param {Object[]} input.restriction.attributes - Array of attributes + * @param {String} input.restriction.attributes.property - property of attribute + * @param {String} input.restriction.attributes.value - value of attribute + * @param {String} input.restriction.attributes.propertyType - propertyType of attribute + * @param {String} input.restriction.attributes.operator - operator of attributes + * @param {Object} input.restriction.destination - destination object + * @param {String[]} input.restriction.destination.country - Array of countries + * @param {String[]} input.restriction.destination.region - Array of regions + * @param {String[]} input.restriction.destination.postal - Array of postal codes * @returns {Promise} An object with a `restriction` property containing the created restriction */ export default async function createFlatRateFulfillmentRestriction(context, input) { diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/deleteFlatRateFulfillmentMethod.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/deleteFlatRateFulfillmentMethod.js index 176039840ee..52a8d7c95d3 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/deleteFlatRateFulfillmentMethod.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/deleteFlatRateFulfillmentMethod.js @@ -10,7 +10,9 @@ const inputSchema = new SimpleSchema({ * @method deleteFlatRateFulfillmentMethod * @summary deletes a flat rate fulfillment method * @param {Object} context - an object containing the per-request state - * @param {Object} input - Input (see SimpleSchema) + * @param {Object} input - Input + * @param {String} input.methoId - Method Id to be deleted + * @param {String} input.shopId - Shop Id * @returns {Promise} An object with a `method` property containing the deleted method */ export default async function deleteFlatRateFulfillmentMethod(context, input) { diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/deleteFlatRateFulfillmentRestriction.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/deleteFlatRateFulfillmentRestriction.js index 4b42cd84308..a9907220280 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/deleteFlatRateFulfillmentRestriction.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/deleteFlatRateFulfillmentRestriction.js @@ -10,7 +10,9 @@ const inputSchema = new SimpleSchema({ * @method deleteFlatRateFulfillmentRestriction * @summary deletes a flat rate fulfillment restriction * @param {Object} context - an object containing the per-request state - * @param {Object} input - Input (see SimpleSchema) + * @param {Object} input - Input + * @param {String} input.restrictionId - RestrictionId Id to be deleted + * @param {String} input.shopId - Shop Id * @returns {Promise} An object with a `restriction` property containing the deleted restriction */ export default async function deleteFlatRateFulfillmentRestriction(context, input) { diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentMethod.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentMethod.js index 9857f497dd1..8fd38f38b3f 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentMethod.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentMethod.js @@ -12,7 +12,19 @@ const inputSchema = new SimpleSchema({ * @method updateFlatRateFulfillmentMethod * @summary updates a flat rate fulfillment method * @param {Object} context - an object containing the per-request state - * @param {Object} input - Input (see SimpleSchema) + * @param {Object} input - Input object + * @param {String} input.shopId - Shop Id + * @param {String} input.methodId - Method Id to be updated + * @param {Number} input.method.cost - Cost + * @param {String} input.method.group - Group name (free / ground) + * @param {Number} input.method.handling - Handling price + * @param {Boolean} input.method.isEnabled - Status + * @param {Boolean} input.method.enabled - Status + * @param {String} input.method.label - Display label of method + * @param {String} input.method.name - Name of method + * @param {String} input.method.fulfillmentMethod - Fulfullment method name non-editable + * @param {String[]} input.method.fulfillmentTypes - Array of Fulfullment type + * @param {Number} input.method.rate - Rate of method * @returns {Promise} An object with a `method` property containing the updated method */ export default async function updateFlatRateFulfillmentMethod(context, input) { diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentRestriction.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentRestriction.js index 40c553eddbf..83d29e427ad 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentRestriction.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentRestriction.js @@ -13,7 +13,19 @@ const inputSchema = new SimpleSchema({ * @method updateFlatRateFulfillmentRestriction * @summary updates a flat rate fulfillment method * @param {Object} context - an object containing the per-request state - * @param {Object} input - Input (see SimpleSchema) + * @param {Object} input - Input + * @param {String} input.shopId - Shop Id + * @param {String} input.restrictionId - Restriction Id + * @param {String[]} input.restriction.methodIds - Array of methodIds + * @param {Object[]} input.restriction.attributes - Array of attributes + * @param {String} input.restriction.attributes.property - property of attribute + * @param {String} input.restriction.attributes.value - value of attribute + * @param {String} input.restriction.attributes.propertyType - propertyType of attribute + * @param {String} input.restriction.attributes.operator - operator of attributes + * @param {Object} input.restriction.destination - destination object + * @param {String[]} input.restriction.destination.country - Array of countries + * @param {String[]} input.restriction.destination.region - Array of regions + * @param {String[]} input.restriction.destination.postal - Array of postal codes * @returns {Promise} An object with a `restriction` property containing the updated method */ export default async function updateFlatRateFulfillmentRestriction(context, input) { diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/flatRateFulfillmentRestriction.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/flatRateFulfillmentRestriction.js new file mode 100644 index 00000000000..d91050165a7 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/flatRateFulfillmentRestriction.js @@ -0,0 +1,21 @@ +/** + * @name flatRateFulfillmentRestriction + * @method + * @memberof Fulfillment + * @summary Query the FulfillmentRestrictions collection for restrictions with the provided shopId + * @param {Object} context - an object containing the per-request state + * @param {Object} params - request parameters + * @param {String} params.shopId - Shop ID for the shop that owns the restriction + * @param {String} params.restrictionId - Restriction ID of the restriction + * @returns {Promise|undefined} - A restrictions document, if one is found + */ +export default async function flatRateFulfillmentRestriction(context, { restrictionId, shopId } = {}) { + const { collections: { FulfillmentRestrictions } } = context; + + await context.validatePermissions("reaction:legacy:fulfillmentRestrictions", "read", { shopId }); + + return FulfillmentRestrictions.findOne({ + _id: restrictionId, + shopId + }); +} diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/flatRateFulfillmentRestrictions.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/flatRateFulfillmentRestrictions.js new file mode 100644 index 00000000000..eafb95340be --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/flatRateFulfillmentRestrictions.js @@ -0,0 +1,19 @@ +/** + * @name flatRateFulfillmentRestrictions + * @method + * @memberof Fulfillment + * @summary Query the FulfillmentRestrictions collection for restrictions with the provided shopId + * @param {Object} context - an object containing the per-request state + * @param {Object} params - request parameters + * @param {String} params.shopId - Shop ID for the shop that owns the restrictions + * @returns {Promise|undefined} - A restrictions document, if one is found + */ +export default async function flatRateFulfillmentRestrictions(context, { shopId } = {}) { + const { collections: { FulfillmentRestrictions } } = context; + + await context.validatePermissions("reaction:legacy:fulfillmentRestrictions", "read", { shopId }); + + return FulfillmentRestrictions.find({ + shopId + }); +} diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/getFlatRateFulfillmentRestriction.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/getFlatRateFulfillmentRestriction.js index a558fd67266..5e1787c95e2 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/getFlatRateFulfillmentRestriction.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/getFlatRateFulfillmentRestriction.js @@ -1,3 +1,5 @@ +import flatRateFulfillmentRestriction from "./flatRateFulfillmentRestriction"; + /** * @name getFlatRateFulfillmentRestriction * @method @@ -5,16 +7,11 @@ * @summary Query the FulfillmentRestrictions collection for restrictions with the provided shopId * @param {Object} context - an object containing the per-request state * @param {Object} params - request parameters + * @param {String} params.restrictionId - ID of the restriction * @param {String} params.shopId - Shop ID for the shop that owns the restriction * @returns {Promise|undefined} - A restrictions document, if one is found + * @deprecated since version 5.0, use flatRateFulfillmentRestriction instead */ export default async function getFlatRateFulfillmentRestriction(context, { restrictionId, shopId } = {}) { - const { collections: { FulfillmentRestrictions } } = context; - - await context.validatePermissions("reaction:legacy:fulfillmentRestrictions", "read", { shopId }); - - return FulfillmentRestrictions.findOne({ - _id: restrictionId, - shopId - }); + return flatRateFulfillmentRestriction(context, { restrictionId, shopId }); } diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/getFlatRateFulfillmentRestrictions.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/getFlatRateFulfillmentRestrictions.js index 9d352db7438..95a0fa294b2 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/getFlatRateFulfillmentRestrictions.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/getFlatRateFulfillmentRestrictions.js @@ -1,3 +1,5 @@ +import flatRateFulfillmentRestrictions from "./flatRateFulfillmentRestrictions"; + /** * @name getFlatRateFulfillmentRestrictions * @method @@ -7,13 +9,8 @@ * @param {Object} params - request parameters * @param {String} params.shopId - Shop ID for the shop that owns the restrictions * @returns {Promise|undefined} - A restrictions document, if one is found + * @deprecated since version 5.0, use flatRateFulfillmentRestrictions instead */ export default async function getFlatRateFulfillmentRestrictions(context, { shopId } = {}) { - const { collections: { FulfillmentRestrictions } } = context; - - await context.validatePermissions("reaction:legacy:fulfillmentRestrictions", "read", { shopId }); - - return FulfillmentRestrictions.find({ - shopId - }); + return flatRateFulfillmentRestrictions(context, { shopId }); } diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Query/flatRateFulfillmentRestriction.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Query/flatRateFulfillmentRestriction.js new file mode 100644 index 00000000000..908d08a875a --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Query/flatRateFulfillmentRestriction.js @@ -0,0 +1,16 @@ +/** + * @name Query/getFlatRateFulfillmentRestriction + * @method + * @memberof Fulfillment/GraphQL + * @summary resolver for the getFlatRateFulfillmentRestriction GraphQL mutation + * @param {Object} parentResult - unused + * @param {ConnectionArgs} args - an object of all arguments that were sent by the client + * @param {String} args.shopId - The shop that owns this restriction + * @param {Object} context - an object containing the per-request state + * @returns {Promise|undefined} A Restriction object + */ +export default async function getFlatRateFulfillmentRestriction(parentResult, args, context) { + const { restrictionId, shopId } = args; + + return context.queries.getFlatRateFulfillmentRestriction(context, { restrictionId, shopId }); +} diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Query/flatRateFulfillmentRestrictions.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Query/flatRateFulfillmentRestrictions.js new file mode 100644 index 00000000000..1c445168298 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Query/flatRateFulfillmentRestrictions.js @@ -0,0 +1,26 @@ +import getPaginatedResponse from "@reactioncommerce/api-utils/graphql/getPaginatedResponse.js"; +import wasFieldRequested from "@reactioncommerce/api-utils/graphql/wasFieldRequested.js"; + +/** + * @name Query/flatRateFulfillmentRestrictions + * @method + * @memberof Fulfillment/GraphQL + * @summary resolver for the flatRateFulfillmentRestrictions GraphQL mutation + * @param {Object} parentResult - unused + * @param {Object} args - an object of all arguments that were sent by the client + * @param {String} args.shopId - The shop that owns these restriction + * @param {Object} context - an object containing the per-request state + * @param {Object} info Info about the GraphQL request + * @returns {Promise|undefined} A Restriction object + */ +export default async function flatRateFulfillmentRestrictions(parentResult, args, context, info) { + const { shopId, ...connectionArgs } = args; + + const cursor = await context.queries.flatRateFulfillmentRestrictions(context, { shopId }); + + return getPaginatedResponse(cursor, connectionArgs, { + includeHasNextPage: wasFieldRequested("pageInfo.hasNextPage", info), + includeHasPreviousPage: wasFieldRequested("pageInfo.hasPreviousPage", info), + includeTotalCount: wasFieldRequested("totalCount", info) + }); +} diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Query/getFlatRateFulfillmentRestriction.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Query/getFlatRateFulfillmentRestriction.js index e0955d25650..74bf826c02d 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Query/getFlatRateFulfillmentRestriction.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Query/getFlatRateFulfillmentRestriction.js @@ -10,6 +10,7 @@ import { decodeFulfillmentRestrictionOpaqueId, decodeShopOpaqueId } from "../../ * @param {String} args.shopId - The shop that owns this restriction * @param {Object} context - an object containing the per-request state * @returns {Promise|undefined} A Restriction object + * @deprecated since version 5.0, use flatRateFulfillmentRestriction instead */ export default async function getFlatRateFulfillmentRestriction(parentResult, args, context) { const { restrictionId, shopId } = args; diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Query/getFlatRateFulfillmentRestrictions.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Query/getFlatRateFulfillmentRestrictions.js index 6f4b8792c3e..cef1c34d37b 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Query/getFlatRateFulfillmentRestrictions.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/resolvers/Query/getFlatRateFulfillmentRestrictions.js @@ -13,6 +13,7 @@ import { decodeShopOpaqueId } from "../../xforms/id.js"; * @param {Object} context - an object containing the per-request state * @param {Object} info Info about the GraphQL request * @returns {Promise|undefined} A Restriction object + * @deprecated since version 5.0, use flatRateFulfillmentRestrictions instead */ export default async function getFlatRateFulfillmentRestrictions(parentResult, args, context, info) { const { shopId, ...connectionArgs } = args; From 3862d7b112122bee76bb127b6b2b324f8143f3b3 Mon Sep 17 00:00:00 2001 From: Sujith Date: Mon, 21 Nov 2022 15:06:29 +0530 Subject: [PATCH 53/71] fix: review comment fixes Signed-off-by: Sujith --- .../src/fulfillmentMethodsWithQuotesShippingDynamicRate.js | 2 +- .../src/startup.js | 3 --- .../src/util/checkAndCreateFulfillmentMethod.js | 4 ++-- packages/api-plugin-fulfillment-type-shipping/src/startup.js | 3 --- 4 files changed, 3 insertions(+), 9 deletions(-) diff --git a/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/fulfillmentMethodsWithQuotesShippingDynamicRate.js b/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/fulfillmentMethodsWithQuotesShippingDynamicRate.js index 96feabb908d..3c115cb6e78 100644 --- a/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/fulfillmentMethodsWithQuotesShippingDynamicRate.js +++ b/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/fulfillmentMethodsWithQuotesShippingDynamicRate.js @@ -38,7 +38,7 @@ export default async function fulfillmentMethodsWithQuotesShippingDynamicRate(co } const initialNumOfRates = rates.length; - shippingRateDocs.map(async (doc) => { + shippingRateDocs.forEach(async (doc) => { const carrier = doc.provider.label; const currentPluginMethods = doc.methods.filter((method) => ((method.fulfillmentMethod === (fulfillmentMethodName)) && (method.enabled))); for (const method of currentPluginMethods) { diff --git a/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/startup.js b/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/startup.js index 04ffae4ca1b..2447634c5c2 100644 --- a/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/startup.js +++ b/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/startup.js @@ -12,9 +12,6 @@ export default async function fulfillmentMethodShippingDynamicRateStartup(contex const { shop } = payload; const shopId = shop._id; - // We do not have validatePermissions in context during this startup stage, hence commenting below - // await context.validatePermissions("reaction:legacy:fulfillmentTypes", "read", { shopId }); - const insertedMethod = await checkAndCreateFulfillmentMethod(context, shopId); if (!insertedMethod) { throw new ReactionError("server-error", "Error in creating fulfillment method"); diff --git a/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/util/checkAndCreateFulfillmentMethod.js b/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/util/checkAndCreateFulfillmentMethod.js index 1ff8453ae6c..57ec6d41d1f 100644 --- a/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/util/checkAndCreateFulfillmentMethod.js +++ b/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/util/checkAndCreateFulfillmentMethod.js @@ -1,9 +1,9 @@ import ReactionError from "@reactioncommerce/reaction-error"; /** - * @summary Called on startup to create the root entry of this fulfillment type in Fulfillment collection + * @summary Finds the corresponding ff-type and calls mutation to insert the ff-method * @param {Object} context Startup context * @param {String} shopId Shop ID - * @returns {Boolean} true if entry exist or insert success else false + * @returns {Object} method which was inserted */ export default async function checkAndCreateFulfillmentMethod(context, shopId) { const { collections: { Fulfillment } } = context; diff --git a/packages/api-plugin-fulfillment-type-shipping/src/startup.js b/packages/api-plugin-fulfillment-type-shipping/src/startup.js index e3cf4bed319..399b20b4c48 100644 --- a/packages/api-plugin-fulfillment-type-shipping/src/startup.js +++ b/packages/api-plugin-fulfillment-type-shipping/src/startup.js @@ -12,9 +12,6 @@ export default async function fulfillmentTypeShippingStartup(context) { const { shop } = payload; const shopId = shop._id; - // We do not have validatePermissions in context during this startup stage, hence commenting below - // await context.validatePermissions("reaction:legacy:fulfillmentTypes", "read", { shopId }); - const insertSuccess = await checkAndCreateFulfillmentType(context, shopId); if (!insertSuccess) { throw new ReactionError("server-error", "Error in creating fulfillment type"); From 1e216082bf5fb4c4f463797b7fe8d62e93bf1d1f Mon Sep 17 00:00:00 2001 From: Sujith Date: Fri, 10 Mar 2023 14:23:37 +0530 Subject: [PATCH 54/71] fix: review comment fixes Signed-off-by: Sujith --- .../src/mutations/createFulfillmentMethod.js | 13 +++++-------- .../src/mutations/createFulfillmentType.js | 4 ++-- .../src/mutations/updateFulfillmentMethod.js | 7 +++---- .../mutations/updateFulfillmentOptionsForGroup.js | 2 +- .../updateFulfillmentOptionsForGroup.test.js | 12 ++++++------ .../Mutation/selectFulfillmentOptionForGroup.js | 11 +++++++++++ 6 files changed, 28 insertions(+), 21 deletions(-) diff --git a/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.js b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.js index 75368c763d4..8d7c57d7521 100644 --- a/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.js +++ b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.js @@ -35,17 +35,14 @@ export default async function createFulfillmentMethodMutation(context, input) { await context.validatePermissions("reaction:legacy:fulfillmentMethods", "create", { shopId }); - const ffTypeRecord = await Fulfillment.findOne({ _id: fulfillmentTypeId, shopId }); - if (!ffTypeRecord) throw new ReactionError("server-error", "Unable to create fulfillment method without defined type"); + const fulfillmentType = await Fulfillment.findOne({ _id: fulfillmentTypeId, shopId }); + if (!fulfillmentType) throw new ReactionError("server-error", "Unable to create fulfillment method without defined type"); - let ffTypeMethodRecord; - if (ffTypeRecord.methods && Array.isArray(ffTypeRecord.methods)) { - ffTypeMethodRecord = ffTypeRecord.methods.find((currMethod) => currMethod.fulfillmentMethod === method.fulfillmentMethod); - } - if (ffTypeMethodRecord) throw new ReactionError("server-error", "Fulfillment Method already exists"); + const fulfillmentMethod = fulfillmentType.methods?.find((currMethod) => currMethod.fulfillmentMethod === method.fulfillmentMethod); + if (fulfillmentMethod) throw new ReactionError("server-error", "Fulfillment Method already exists"); method._id = Random.id(); - method.fulfillmentTypes = [ffTypeRecord.fulfillmentType]; + method.fulfillmentTypes = [fulfillmentType.fulfillmentType]; const { matchedCount } = await Fulfillment.updateOne({ shopId, diff --git a/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.js b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.js index 9c5b8fe8665..9d45f0fc20e 100644 --- a/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.js +++ b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.js @@ -32,8 +32,8 @@ export default async function createFulfillmentType(context, input) { const { collections: { Fulfillment } } = context; const { shopId, fulfillmentType } = cleanedInput; - const ffTypeRecord = await Fulfillment.findOne({ shopId, fulfillmentType }); - if (ffTypeRecord) throw new ReactionError("invalid-parameter", "Fulfillment Type already exists"); + const existingFulfillmentType = await Fulfillment.findOne({ shopId, fulfillmentType }); + if (existingFulfillmentType) throw new ReactionError("invalid-parameter", "Fulfillment Type already exists"); await context.validatePermissions("reaction:legacy:fulfillmentTypes", "create", { shopId }); diff --git a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.js b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.js index d7b0f887e0f..4e3c7beec61 100644 --- a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.js +++ b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.js @@ -31,15 +31,14 @@ export default async function updateFulfillmentMethodMutation(context, input) { await context.validatePermissions("reaction:legacy:fulfillmentMethods", "update", { shopId }); - const ffTypeMethodRecord = await Fulfillment.findOne({ + const fulfillmentType = await Fulfillment.findOne({ "_id": fulfillmentTypeId, shopId, "methods._id": methodId }); - if (!ffTypeMethodRecord) throw new ReactionError("server-error", "Fulfillment Method does not exist"); + if (!fulfillmentType) throw new ReactionError("server-error", "Fulfillment Type / Method does not exist"); - const currentFulfillmentMethod = (ffTypeMethodRecord.methods || []).find((meth) => meth._id === methodId); - if (!currentFulfillmentMethod) throw new ReactionError("server-error", "Fulfillment Method does not exist"); + const currentFulfillmentMethod = (fulfillmentType.methods || []).find((meth) => meth._id === methodId); const updatedMethod = { ...currentFulfillmentMethod, ...method }; // update only provided user editable fields const { matchedCount } = await Fulfillment.updateOne({ diff --git a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.js b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.js index 78de9b922e7..a6eac679970 100644 --- a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.js +++ b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.js @@ -77,7 +77,7 @@ export default async function updateFulfillmentOptionsForGroup(context, input) { const commonOrder = await context.queries.getCommonOrderForCartGroup(context, { cart, fulfillmentGroupId: fulfillmentGroup._id }); // In the future we want to do this async and subscribe to the results - const rates = await context.queries.getFulfillmentMethodsWithQuotes(commonOrder, context); + const rates = await context.queries.fulfillmentMethodsWithQuotes(commonOrder, context); const { shipmentQuotes, shipmentQuotesQueryStatus } = getShipmentQuotesQueryStatus(rates); diff --git a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.test.js b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.test.js index 1553742d6cd..1baac89f67b 100644 --- a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.test.js +++ b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.test.js @@ -5,12 +5,12 @@ import updateFulfillmentOptionsForGroup from "./updateFulfillmentOptionsForGroup const fakeCart = Factory.Cart.makeOne(); const fakeQuote = Factory.ShipmentQuote.makeOne(); -const mockGetFulfillmentMethodsWithQuotes = jest.fn().mockName("getFulfillmentMethodsWithQuotes"); +const mockFulfillmentMethodsWithQuotes = jest.fn().mockName("fulfillmentMethodsWithQuotes"); const mockGetCommonOrderForCartGroup = jest.fn().mockName("getCommonOrderForCartGroup"); beforeAll(() => { mockContext.queries = { - getFulfillmentMethodsWithQuotes: mockGetFulfillmentMethodsWithQuotes, + fulfillmentMethodsWithQuotes: mockFulfillmentMethodsWithQuotes, getCommonOrderForCartGroup: mockGetCommonOrderForCartGroup }; if (!mockContext.mutations.saveCart) { @@ -22,7 +22,7 @@ beforeAll(() => { }); beforeEach(() => { - mockGetFulfillmentMethodsWithQuotes.mockClear(); + mockFulfillmentMethodsWithQuotes.mockClear(); mockContext.queries.getCartById = jest.fn().mockName("getCartById").mockReturnValueOnce(Promise.resolve({ _id: "cartId", @@ -47,7 +47,7 @@ beforeEach(() => { }); test("updates cart properly for empty rates", async () => { - mockGetFulfillmentMethodsWithQuotes.mockReturnValueOnce(Promise.resolve([])); + mockFulfillmentMethodsWithQuotes.mockReturnValueOnce(Promise.resolve([])); mockContext.collections.Cart.findOne.mockReturnValueOnce(Promise.resolve(fakeCart)); const result = await updateFulfillmentOptionsForGroup(mockContext, { @@ -83,7 +83,7 @@ test("updates cart properly for empty rates", async () => { }); test("updates cart properly for error rates", async () => { - mockGetFulfillmentMethodsWithQuotes.mockReturnValueOnce(Promise.resolve([{ + mockFulfillmentMethodsWithQuotes.mockReturnValueOnce(Promise.resolve([{ requestStatus: "error", shippingProvider: "all", message: "All requests for shipping methods failed." @@ -126,7 +126,7 @@ test("updates cart properly for error rates", async () => { }); test("updates cart properly for success rates", async () => { - mockGetFulfillmentMethodsWithQuotes.mockReturnValueOnce(Promise.resolve([fakeQuote])); + mockFulfillmentMethodsWithQuotes.mockReturnValueOnce(Promise.resolve([fakeQuote])); mockContext.collections.Cart.findOne.mockReturnValueOnce(Promise.resolve(fakeCart)); const result = await updateFulfillmentOptionsForGroup(mockContext, { diff --git a/packages/api-plugin-fulfillment/src/resolvers/Mutation/selectFulfillmentOptionForGroup.js b/packages/api-plugin-fulfillment/src/resolvers/Mutation/selectFulfillmentOptionForGroup.js index 260efda3b50..9b30c05fbc6 100644 --- a/packages/api-plugin-fulfillment/src/resolvers/Mutation/selectFulfillmentOptionForGroup.js +++ b/packages/api-plugin-fulfillment/src/resolvers/Mutation/selectFulfillmentOptionForGroup.js @@ -12,6 +12,17 @@ import selectFulfillmentOptionForGroupMutation from "../../mutations/selectFulfi * @param {String} [args.input.cartToken] - The token for the cart, required if it is an anonymous cart * @param {String} args.input.fulfillmentGroupId - The group to select a fulfillment option for * @param {String} args.input.fulfillmentMethodId - The fulfillment method ID from the option the shopper selected + * @param {Object} context - an object containing the per-request state + *//** + * The old, deprecated way to call selectFulfillmentOptionForGroup. + * + * @deprecated (reason: "Avoid using opaqueIds and clientMutationId") + * @param {Object} parentResult - unused + * @param {Object} args.input - an object of all mutation arguments that were sent by the client + * @param {String} args.input.cartId - The ID of the cart to select a fulfillment option for + * @param {String} [args.input.cartToken] - The token for the cart, required if it is an anonymous cart + * @param {String} args.input.fulfillmentGroupId - The group to select a fulfillment option for + * @param {String} args.input.fulfillmentMethodId - The fulfillment method ID from the option the shopper selected * @param {String} [args.input.clientMutationId] - An optional string identifying the mutation call * @param {Object} context - an object containing the per-request state * @returns {Promise} SelectFulfillmentOptionForGroupPayload From 37536fa2a047cd6b193b0c23d5dcfcc8e4d9736d Mon Sep 17 00:00:00 2001 From: Sujith Date: Fri, 10 Mar 2023 17:04:29 +0530 Subject: [PATCH 55/71] fix: test errors and pnpm-lock Signed-off-by: Sujith --- .../src/mutations/updateFulfillmentMethod.js | 1 + .../mutations/updateFulfillmentMethod.test.js | 2 +- pnpm-lock.yaml | 40 ------------------- 3 files changed, 2 insertions(+), 41 deletions(-) diff --git a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.js b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.js index 4e3c7beec61..93bd514de25 100644 --- a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.js +++ b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.js @@ -39,6 +39,7 @@ export default async function updateFulfillmentMethodMutation(context, input) { if (!fulfillmentType) throw new ReactionError("server-error", "Fulfillment Type / Method does not exist"); const currentFulfillmentMethod = (fulfillmentType.methods || []).find((meth) => meth._id === methodId); + if (!currentFulfillmentMethod) throw new ReactionError("server-error", "Fulfillment Method does not exist"); const updatedMethod = { ...currentFulfillmentMethod, ...method }; // update only provided user editable fields const { matchedCount } = await Fulfillment.updateOne({ diff --git a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.test.js b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.test.js index 6c9df48dba9..62ec5d013f0 100644 --- a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.test.js +++ b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.test.js @@ -43,7 +43,7 @@ test("throws if the fulfillmentType does not exists", async () => { rate: 99 } }; - const expectedError = new ReactionError("server-error", "Fulfillment Method does not exist"); + const expectedError = new ReactionError("server-error", "Fulfillment Type / Method does not exist"); await expect(updateFulfillmentMethodMutation(mockContext, fulfillmentMethodInput)).rejects.toThrow(expectedError); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 77ef2cd27c7..2b3bbeb22be 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -705,46 +705,6 @@ importers: '@reactioncommerce/api-plugin-carts': link:../api-plugin-carts '@reactioncommerce/data-factory': 1.0.1 - packages/api-plugin-fulfillment-method-shipping-flat-rate: - specifiers: - '@reactioncommerce/api-utils': ^1.16.9 - '@reactioncommerce/logger': ^1.1.3 - '@reactioncommerce/random': ~1.0.2 - '@reactioncommerce/reaction-error': ^1.0.1 - simpl-schema: ^1.12.2 - dependencies: - '@reactioncommerce/api-utils': link:../api-utils - '@reactioncommerce/logger': link:../logger - '@reactioncommerce/random': link:../random - '@reactioncommerce/reaction-error': link:../reaction-error - simpl-schema: 1.12.3 - - packages/api-plugin-fulfillment-method-shipping-ups: - specifiers: - '@reactioncommerce/api-utils': ^1.16.9 - '@reactioncommerce/reaction-error': ^1.0.1 - simpl-schema: ^1.12.2 - dependencies: - '@reactioncommerce/api-utils': link:../api-utils - '@reactioncommerce/reaction-error': link:../reaction-error - simpl-schema: 1.12.3 - - packages/api-plugin-fulfillment-type-shipping: - specifiers: - '@reactioncommerce/api-utils': ^1.16.9 - '@reactioncommerce/logger': ^1.1.3 - '@reactioncommerce/random': ~1.0.2 - '@reactioncommerce/reaction-error': ^1.0.1 - lodash: ^4.17.21 - simpl-schema: ^1.12.2 - dependencies: - '@reactioncommerce/api-utils': link:../api-utils - '@reactioncommerce/logger': link:../logger - '@reactioncommerce/random': link:../random - '@reactioncommerce/reaction-error': link:../reaction-error - lodash: 4.17.21 - simpl-schema: 1.12.3 - packages/api-plugin-i18n: specifiers: '@reactioncommerce/api-utils': ^1.16.5 From 9f16175e537d4f126141e2216171b9389f07d7d0 Mon Sep 17 00:00:00 2001 From: Sujith Date: Fri, 10 Mar 2023 21:50:16 +0530 Subject: [PATCH 56/71] fix: review comment updates Signed-off-by: Sujith --- ...fulfillmentMethodsWithQuotesPickupStore.js | 35 +++++++++---------- .../src/startup.js | 3 -- .../src/checkAndCreateFulfillmentType.js | 27 +++++++------- .../src/startup.js | 3 -- 4 files changed, 30 insertions(+), 38 deletions(-) diff --git a/packages/api-plugin-fulfillment-method-pickup-store/src/fulfillmentMethodsWithQuotesPickupStore.js b/packages/api-plugin-fulfillment-method-pickup-store/src/fulfillmentMethodsWithQuotesPickupStore.js index 08ebe85cfc9..80ef4240cb7 100644 --- a/packages/api-plugin-fulfillment-method-pickup-store/src/fulfillmentMethodsWithQuotesPickupStore.js +++ b/packages/api-plugin-fulfillment-method-pickup-store/src/fulfillmentMethodsWithQuotesPickupStore.js @@ -30,34 +30,33 @@ export default async function fulfillmentMethodsWithQuotesPickupStore(context, c } } - const pickupDocs = await Fulfillment.find({ + const pickupFulfillmentType = await Fulfillment.findOne({ "shopId": commonOrder.shopId, "fulfillmentType": fulfillmentTypeName, "provider.enabled": true - }).toArray(); - if (!pickupDocs || !pickupDocs.length) { + }); + if (!pickupFulfillmentType) { return [rates, retrialTargets]; } const initialNumOfRates = rates.length; - pickupDocs.map(async (doc) => { - const carrier = doc.provider.label; - const currentPluginMethods = doc.methods.filter((method) => ((method.name === fulfillmentMethodName) && (method.enabled))); - for (const method of currentPluginMethods) { - const updatedMethod = collectStoreDetails(method, commonOrder); + const carrier = pickupFulfillmentType.provider?.label || ""; + const currentPluginMethods = pickupFulfillmentType.methods ? + pickupFulfillmentType.methods.filter((method) => ((method.name === fulfillmentMethodName) && (method.enabled))) : []; - rates.push({ - carrier, - handlingPrice: updatedMethod.handling, - method: updatedMethod, - rate: updatedMethod.rate, - shippingPrice: updatedMethod.rate + updatedMethod.handling, - shopId: doc.shopId - }); - } - }); + for (const method of currentPluginMethods) { + const updatedMethod = collectStoreDetails(method, commonOrder); + rates.push({ + carrier, + handlingPrice: updatedMethod.handling, + method: updatedMethod, + rate: updatedMethod.rate, + shippingPrice: updatedMethod.rate + updatedMethod.handling, + shopId: pickupFulfillmentType.shopId + }); + } if (rates.length === initialNumOfRates) { const errorDetails = { diff --git a/packages/api-plugin-fulfillment-method-pickup-store/src/startup.js b/packages/api-plugin-fulfillment-method-pickup-store/src/startup.js index 10547ae3ee0..b01c43f3053 100644 --- a/packages/api-plugin-fulfillment-method-pickup-store/src/startup.js +++ b/packages/api-plugin-fulfillment-method-pickup-store/src/startup.js @@ -10,9 +10,6 @@ export default async function fulfillmentMethodPickupStoreStartup(context) { const { shop } = payload; const shopId = shop._id; - // We do not have validatePermissions in context during this startup stage, hence commenting below - // await context.validatePermissions("reaction:legacy:fulfillmentTypes", "read", { shopId }); - const insertedMethod = await checkAndCreateFulfillmentMethod(context, shopId); if (!insertedMethod) { throw new ReactionError("server-error", "Error in creating fulfillment method"); diff --git a/packages/api-plugin-fulfillment-type-pickup/src/checkAndCreateFulfillmentType.js b/packages/api-plugin-fulfillment-type-pickup/src/checkAndCreateFulfillmentType.js index 945cc4f98e7..1ae1516f9d6 100644 --- a/packages/api-plugin-fulfillment-type-pickup/src/checkAndCreateFulfillmentType.js +++ b/packages/api-plugin-fulfillment-type-pickup/src/checkAndCreateFulfillmentType.js @@ -7,19 +7,18 @@ export default async function checkAndCreateFulfillmentType(context, shopId) { const { collections: { Fulfillment } } = context; - const pickupRecord = await Fulfillment.findOne({ fulfillmentType: "pickup", shopId }); - if (!pickupRecord) { - const groupInfo = { - name: "Pickup Provider", - shopId, - provider: { - enabled: true, - label: "Pickup", - name: "pickup" - }, - fulfillmentType: "pickup" - }; - await context.mutations.createFulfillmentType(context.getInternalContext(), groupInfo); - } + const pickupFulfillmentType = await Fulfillment.findOne({ fulfillmentType: "pickup", shopId }); + if (pickupFulfillmentType) return true; + const newPickupFulfillmentType = { + name: "Pickup Provider", + shopId, + provider: { + enabled: true, + label: "Pickup", + name: "pickup" + }, + fulfillmentType: "pickup" + }; + await context.mutations.createFulfillmentType(context.getInternalContext(), newPickupFulfillmentType); return true; } diff --git a/packages/api-plugin-fulfillment-type-pickup/src/startup.js b/packages/api-plugin-fulfillment-type-pickup/src/startup.js index 401c8f4d7ad..37f937ff585 100644 --- a/packages/api-plugin-fulfillment-type-pickup/src/startup.js +++ b/packages/api-plugin-fulfillment-type-pickup/src/startup.js @@ -11,9 +11,6 @@ export default async function fulfillmentTypePickupStartup(context) { const { shop } = payload; const shopId = shop._id; - // We do not have validatePermissions in context during this startup stage, hence commenting below - // await context.validatePermissions("reaction:legacy:fulfillmentTypes", "read", { shopId }); - const insertSuccess = await checkAndCreateFulfillmentType(context, shopId); if (!insertSuccess) { throw new ReactionError("server-error", "Error in creating fulfillment type"); From 22b0c79f5e13f55a90d8378de3f10b21d4f639d1 Mon Sep 17 00:00:00 2001 From: Sujith Date: Fri, 10 Mar 2023 22:27:56 +0530 Subject: [PATCH 57/71] fix: unit test fix Signed-off-by: Sujith --- .../src/fulfillmentMethodsWithQuotesPickupStore.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/api-plugin-fulfillment-method-pickup-store/src/fulfillmentMethodsWithQuotesPickupStore.test.js b/packages/api-plugin-fulfillment-method-pickup-store/src/fulfillmentMethodsWithQuotesPickupStore.test.js index ac9a23d8a16..1a50f81c2db 100644 --- a/packages/api-plugin-fulfillment-method-pickup-store/src/fulfillmentMethodsWithQuotesPickupStore.test.js +++ b/packages/api-plugin-fulfillment-method-pickup-store/src/fulfillmentMethodsWithQuotesPickupStore.test.js @@ -34,7 +34,7 @@ test("should return previousResults if not fulfillment records enabled", async ( }; mockContext.collections.Fulfillment = { - find: jest.fn(() => ({ toArray: () => [] })) + findOne: jest.fn(() => ({})) }; const result = await fulfillmentMethodsWithQuotesPickupStore(mockContext, commonOrder, previousResults); expect(result).toEqual(previousResults); @@ -121,7 +121,7 @@ test("should return rates witout error", async () => { const expectedResult = [[...previousResults[0], expectedNewRate], []]; mockContext.collections.Fulfillment = { - find: jest.fn(() => ({ toArray: () => [pickupDoc] })) + findOne: jest.fn(() => (pickupDoc)) }; const result = await fulfillmentMethodsWithQuotesPickupStore(mockContext, commonOrder, previousResults); expect(result).toEqual(expectedResult); From 24a3202c0a026cd178c068a6030b4b5f5df9a615 Mon Sep 17 00:00:00 2001 From: Sujith Date: Fri, 10 Mar 2023 22:42:03 +0530 Subject: [PATCH 58/71] fix: review comments Signed-off-by: Sujith --- .../src/schemas/schema.graphql | 8 +- .../util/orderValidators/createPayments.js | 11 +- .../orderValidators/getCustomFields.test.js | 4 +- .../src/util/orderValidators/getDiscounts.js | 19 --- .../util/orderValidators/getDiscounts.test.js | 41 ----- .../src/util/orderValidators/prepareOrder.js | 145 +++++++----------- .../validateInitialOrderData.js | 4 +- 7 files changed, 69 insertions(+), 163 deletions(-) delete mode 100644 packages/api-plugin-orders/src/util/orderValidators/getDiscounts.js delete mode 100644 packages/api-plugin-orders/src/util/orderValidators/getDiscounts.test.js diff --git a/packages/api-plugin-orders/src/schemas/schema.graphql b/packages/api-plugin-orders/src/schemas/schema.graphql index a909d9f21b1..9da014853e5 100644 --- a/packages/api-plugin-orders/src/schemas/schema.graphql +++ b/packages/api-plugin-orders/src/schemas/schema.graphql @@ -116,8 +116,8 @@ extend type Query { "Validate the order for correctness & completeness before placing the order" validateOrder( "input with order and payment objects similar to the one used for placeOrder" - input: validateOrderInput! - ): validateOrderPayload! + input: ValidateOrderInput! + ): ValidateOrderPayload! } extend type Mutation { @@ -961,7 +961,7 @@ type MoveOrderItemsPayload { } "Input for the validateOrder query" -input validateOrderInput { +input ValidateOrderInput { "The order to be validated and created, if payment is accepted" order: OrderInput! @@ -983,7 +983,7 @@ type ValidationErrors { } "Response payload for the validateOrder query" -type validateOrderPayload { +type ValidateOrderPayload { "Orders that were created" errors: [ValidationErrors] diff --git a/packages/api-plugin-orders/src/util/orderValidators/createPayments.js b/packages/api-plugin-orders/src/util/orderValidators/createPayments.js index 6bb7723326f..5fd4c2648d9 100644 --- a/packages/api-plugin-orders/src/util/orderValidators/createPayments.js +++ b/packages/api-plugin-orders/src/util/orderValidators/createPayments.js @@ -26,10 +26,11 @@ export default async function createPayments({ paymentsInput, shippingAddress, shop, - flag + mode }) { // Determining which payment methods are enabled for the shop const availablePaymentMethods = shop.availablePaymentMethods || []; + const createOrderMode = mode === "createOrderObject"; // Verify that total of payment inputs equals total due. We need to be sure // to do this before creating any payment authorizations @@ -61,7 +62,7 @@ export default async function createPayments({ // Authorize this payment - skip if validateOrder let payment = {}; - if (flag === "createOrderObject") { + if (createOrderMode) { try { payment = await paymentMethodConfig.functions.createAuthorizedPayment(context, { accountId, // optional @@ -87,8 +88,8 @@ export default async function createPayments({ currencyCode }; - // If flag === validateOrder, we are not authorizing payment and we do not have payment object to validate - if (flag === "createOrderObject") { + // If mode === validateOrder, we are not authorizing payment and we do not have payment object to validate + if (createOrderMode) { PaymentSchema.validate(paymentWithCurrency); } @@ -96,7 +97,7 @@ export default async function createPayments({ }); let payments = {}; - if (flag === "createOrderObject") { + if (createOrderMode) { try { payments = await Promise.all(paymentPromises); payments = payments.filter((payment) => !!payment); // remove nulls diff --git a/packages/api-plugin-orders/src/util/orderValidators/getCustomFields.test.js b/packages/api-plugin-orders/src/util/orderValidators/getCustomFields.test.js index 9bb44073370..f50b10d7d4d 100644 --- a/packages/api-plugin-orders/src/util/orderValidators/getCustomFields.test.js +++ b/packages/api-plugin-orders/src/util/orderValidators/getCustomFields.test.js @@ -13,11 +13,11 @@ test("should return original fields if there are NO functions defined", async () test("should return transformed fields if there are functions defined", async () => { mockContext.getFunctionsOfType = jest.fn().mockReturnValueOnce([ - jest.fn().mockName("transformCustomOrderFields").mockReturnValueOnce(Promise.resolve({ customField2: "customValue2" })) + jest.fn().mockName("transformCustomOrderFields").mockReturnValueOnce(Promise.resolve({ customField1: "customValue1", customField2: "customValue2" })) ]); const orderInput = { orderId: "order123" }; const customFieldsFromClient = { customField1: "customValue1" }; const result = await getCustomFields(mockContext, customFieldsFromClient, orderInput); - expect(result).toEqual({ customField2: "customValue2" }); + expect(result).toEqual({ customField1: "customValue1", customField2: "customValue2" }); }); diff --git a/packages/api-plugin-orders/src/util/orderValidators/getDiscounts.js b/packages/api-plugin-orders/src/util/orderValidators/getDiscounts.js deleted file mode 100644 index dc761a31e41..00000000000 --- a/packages/api-plugin-orders/src/util/orderValidators/getDiscounts.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * @method getDiscounts - * @summary Returns the Discount Total for cart - * @param {Object} context - an object containing the per-request state - * @param {Object} cart - cart object - * @returns {Object} discounts and discountTotal - */ -export default async function getDiscounts(context, cart) { - let discounts = []; - let discountTotal = 0; - if (cart) { - const discountsResult = await context.queries.getDiscountsTotalForCart(context, cart); - if (discountsResult) { - ({ discounts } = discountsResult); - discountTotal = discountsResult.total; - } - } - return { discounts, discountTotal }; -} diff --git a/packages/api-plugin-orders/src/util/orderValidators/getDiscounts.test.js b/packages/api-plugin-orders/src/util/orderValidators/getDiscounts.test.js deleted file mode 100644 index d2f8b076900..00000000000 --- a/packages/api-plugin-orders/src/util/orderValidators/getDiscounts.test.js +++ /dev/null @@ -1,41 +0,0 @@ -/* eslint-disable require-jsdoc */ -import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; -import getDiscounts from "./getDiscounts.js"; - -test("should return empmty fields if no cart details are passed", async () => { - const result = await getDiscounts(mockContext); - expect(result).toEqual({ - discounts: [], - discountTotal: 0 - }); -}); - -test("should return empmty fields if no discount functions defined", async () => { - mockContext.queries.getDiscountsTotalForCart = jest.fn().mockReturnValueOnce(undefined); - - const cartInput = { cartId: "cart123" }; - const result = await getDiscounts(mockContext, cartInput); - expect(result).toEqual({ - discounts: [], - discountTotal: 0 - }); -}); - -test("should return discount details if there are discount functions defined", async () => { - mockContext.queries.getDiscountsTotalForCart = jest.fn().mockName("getDiscountsTotalForCart").mockReturnValueOnce({ - discounts: [{ - discountId: "discountId", amount: 10 - }], - total: 10 - }); - - const cartInput = { cartId: "cart123" }; - const result = await getDiscounts(mockContext, cartInput); - - expect(result).toEqual({ - discounts: [{ - discountId: "discountId", amount: 10 - }], - discountTotal: 10 - }); -}); diff --git a/packages/api-plugin-orders/src/util/orderValidators/prepareOrder.js b/packages/api-plugin-orders/src/util/orderValidators/prepareOrder.js index 82f43ef3f79..3ec3a9a9b71 100644 --- a/packages/api-plugin-orders/src/util/orderValidators/prepareOrder.js +++ b/packages/api-plugin-orders/src/util/orderValidators/prepareOrder.js @@ -3,7 +3,6 @@ import Random from "@reactioncommerce/random"; import getAnonymousAccessToken from "@reactioncommerce/api-utils/getAnonymousAccessToken.js"; import { Order as OrderSchema, orderInputSchema, paymentInputSchema } from "../../simpleSchemas.js"; import validateInitialOrderData from "./validateInitialOrderData.js"; -import getDiscounts from "./getDiscounts.js"; import getFinalFulfillmentGroups from "./getFinalFulfillmentGroups.js"; import createPayments from "./createPayments.js"; import getReferenceId from "./getReferenceId.js"; @@ -18,6 +17,13 @@ const inputSchema = new SimpleSchema({ "payments.$": paymentInputSchema }); +const orderModeSchema = new SimpleSchema({ + mode: { + type: String, + allowedValues: ["createOrderObject", "validateOrder"] + } +}); + /** * @summary Formats validation error to common error format * @param {Object} err Validation error object @@ -62,39 +68,36 @@ function formatErrors(err) { * @summary Validates if the input order details is valid and ready for order processing * @param {Object} context - an object containing the per-request state * @param {Object} input - order details, refer inputSchema - * @param {String} flag - flag which define if the call is from placeOrder or validateOrder + * @param {String} mode - mode which define if the call is from placeOrder or validateOrder * @returns {Promise} output - order, token and validation results */ -export default async function prepareOrder(context, input, flag) { +export default async function prepareOrder(context, input, mode) { const cleanedInput = inputSchema.clean(input); + orderModeSchema.validate({ mode }); + const createOrderMode = mode === "createOrderObject"; const validationResults = []; // Step01: Input data validation against input schema - if (flag === "createOrderObject") { + try { inputSchema.validate(cleanedInput); - } else { - try { - inputSchema.validate(cleanedInput); - } catch (err) { - const validationErrors = formatErrors(err); - validationResults.push(...validationErrors); - return { errors: validationResults, success: false }; - } + } catch (err) { + if (createOrderMode) throw err; + const validationErrors = formatErrors(err); + validationResults.push(...validationErrors); + return { errors: validationResults, success: false }; } // Step02: Initial validation for shop/cart/user-id let initialValidationResult; - if (flag === "createOrderObject") { + try { initialValidationResult = await validateInitialOrderData(context, cleanedInput); - } else { - try { - initialValidationResult = await validateInitialOrderData(context, cleanedInput); - } catch (err) { - const validationErrors = formatErrors(err); - validationResults.push(...validationErrors); - return { errors: validationResults, success: false }; - } + } catch (err) { + if (createOrderMode) throw err; + const validationErrors = formatErrors(err); + validationResults.push(...validationErrors); + return { errors: validationResults, success: false }; } + const { shop, cart } = initialValidationResult; // Step03: Extract the rest of the required variables @@ -115,12 +118,12 @@ export default async function prepareOrder(context, input, flag) { // Step04: Getting discount details. If no data, we get back empty values let getDiscountsResult; try { - getDiscountsResult = await getDiscounts(context, cart); + getDiscountsResult = await context.queries.getDiscountsTotalForCart(context, cart); } catch (err) { const validationErrors = formatErrors(err); validationResults.push(...validationErrors); } - const { discounts, discountTotal } = getDiscountsResult; + const { discounts, total: discountTotal } = getDiscountsResult; // Create array for surcharges to apply to order, if applicable // Array is populated inside `fulfillmentGroups.map()` @@ -136,8 +139,7 @@ export default async function prepareOrder(context, input, flag) { // Step let finalFulfillmentGroups = []; - - if (flag === "createOrderObject") { + try { ({ orderSurcharges, orderTotal, @@ -153,33 +155,16 @@ export default async function prepareOrder(context, input, flag) { fulfillmentGroups, cart })); - } else { - try { - ({ - orderSurcharges, - orderTotal, - shippingAddressForPayments, - finalFulfillmentGroups - } = await getFinalFulfillmentGroups(context, { - orderId, - accountId, - billingAddress, - cartId, - currencyCode, - discountTotal, - fulfillmentGroups, - cart - })); - } catch (err) { - if (!err.eventData) { - err.eventData = { - field: "Fulfillment Group", - value: "Invalid" - }; - } - const validationErrors = formatErrors(err); - validationResults.push(...validationErrors); + } catch (err) { + if (createOrderMode) throw err; + if (!err.eventData) { + err.eventData = { + field: "Fulfillment Group", + value: "Invalid" + }; } + const validationErrors = formatErrors(err); + validationResults.push(...validationErrors); } const allValidateFuncs = context.getFunctionsOfType("validateOrderMethods"); @@ -197,7 +182,7 @@ export default async function prepareOrder(context, input, flag) { } let payments; - if (flag === "createOrderObject") { + try { payments = await createPayments({ accountId, billingAddress, @@ -208,26 +193,12 @@ export default async function prepareOrder(context, input, flag) { paymentsInput, shippingAddress: shippingAddressForPayments, shop, - flag // Pass on the same flag we received for prepareOrder + mode // Pass on the same mode we received for prepareOrder }); - } else { - try { - payments = await createPayments({ - accountId, - billingAddress, - context, - currencyCode, - email, - orderTotal, - paymentsInput, - shippingAddress: shippingAddressForPayments, - shop, - flag // Pass on the same flag we received for prepareOrder - }); - } catch (err) { - const validationErrors = formatErrors(err); - validationResults.push(...validationErrors); - } + } catch (err) { + if (createOrderMode) throw err; + const validationErrors = formatErrors(err); + validationResults.push(...validationErrors); } // Create anonymousAccessToken if no account ID @@ -256,7 +227,7 @@ export default async function prepareOrder(context, input, flag) { } }; - if (flag === "createOrderObject") { + if (createOrderMode) { order.payments = payments; } @@ -269,28 +240,22 @@ export default async function prepareOrder(context, input, flag) { let referenceId; - if (flag === "createOrderObject") { + try { referenceId = await getReferenceId(context, cart, order); - } else { - try { - referenceId = await getReferenceId(context, cart, order); - } catch (err) { - const validationErrors = formatErrors(err); - validationResults.push(...validationErrors); - } + } catch (err) { + if (createOrderMode) throw err; + const validationErrors = formatErrors(err); + validationResults.push(...validationErrors); } order.referenceId = referenceId; let customFields; - if (flag === "createOrderObject") { + try { customFields = await getCustomFields(context, customFieldsFromClient, order); - } else { - try { - customFields = await getCustomFields(context, customFieldsFromClient, order); - } catch (err) { - const validationErrors = formatErrors(err); - validationResults.push(...validationErrors); - } + } catch (err) { + if (createOrderMode) throw err; + const validationErrors = formatErrors(err); + validationResults.push(...validationErrors); } order.customFields = customFields; @@ -298,10 +263,10 @@ export default async function prepareOrder(context, input, flag) { let success = !(validationResults && validationResults.length > 0); let output; - if (flag === "createOrderObject") { + if (createOrderMode) { OrderSchema.validate(order); output = { order, fullToken, errors: validationResults, success }; - } else { // flag expected to be "validateOrder" + } else { // mode expected to be "validateOrder" const OrderWithoutPaymentsSchema = OrderSchema.omit("payments"); try { OrderWithoutPaymentsSchema.validate(order); diff --git a/packages/api-plugin-orders/src/util/orderValidators/validateInitialOrderData.js b/packages/api-plugin-orders/src/util/orderValidators/validateInitialOrderData.js index ddb7f55c8c0..0cc65282c79 100644 --- a/packages/api-plugin-orders/src/util/orderValidators/validateInitialOrderData.js +++ b/packages/api-plugin-orders/src/util/orderValidators/validateInitialOrderData.js @@ -10,7 +10,7 @@ import ReactionError from "@reactioncommerce/reaction-error"; export default async function validateInitialOrderData(context, cleanedInput) { const { order: orderInput } = cleanedInput; const { cartId, shopId } = orderInput; - const { collections: { Cart }, userId } = context; + const { userId } = context; if (!shopId) throw new ReactionError("invalid-param", "ShopID not found in order data", { field: "ShopId", value: shopId }); @@ -19,7 +19,7 @@ export default async function validateInitialOrderData(context, cleanedInput) { let cart; if (cartId) { - cart = await Cart.findOne({ _id: cartId }); + cart = await context.queries.getCartById(context, cartId); if (!cart) { throw new ReactionError("not-found", "Cart not found while trying to validate order data", { field: "CartId", value: cartId }); } From edaa82bae987c4ec8468373715d11c79509da4e5 Mon Sep 17 00:00:00 2001 From: Sujith Date: Fri, 10 Mar 2023 23:58:10 +0530 Subject: [PATCH 59/71] fix: review comment updates Signed-off-by: Sujith --- apps/reaction/package.json | 92 +++++++++---------- apps/reaction/plugins.json | 8 +- .../mutations/setFulfillmentTypeForItems.js | 8 +- .../setFulfillmentTypeForItems.test.js | 8 +- .../Mutation/setFulfillmentTypeForItems.js | 8 +- .../api-plugin-carts/src/schemas/cart.graphql | 2 +- .../src/util/updateCartFulfillmentGroups.js | 60 ++++++------ 7 files changed, 85 insertions(+), 101 deletions(-) diff --git a/apps/reaction/package.json b/apps/reaction/package.json index 4c5ef86aa87..84c162b1a0d 100644 --- a/apps/reaction/package.json +++ b/apps/reaction/package.json @@ -20,55 +20,49 @@ "url": "https://github.com/reactioncommerce/reaction/issues" }, "dependencies": { - "@reactioncommerce/api-core": "^2.0.0", - "@reactioncommerce/api-plugin-accounts": "^2.0.0", - "@reactioncommerce/api-plugin-address-validation": "^1.3.1", - "@reactioncommerce/api-plugin-address-validation-test": "^1.0.3", - "@reactioncommerce/api-plugin-authentication": "^2.2.3", - "@reactioncommerce/api-plugin-authorization-simple": "^1.3.1", - "@reactioncommerce/api-plugin-carts": "^1.2.4", - "@reactioncommerce/api-plugin-catalogs": "^1.1.2", - "@reactioncommerce/api-plugin-discounts": "^1.0.1", - "@reactioncommerce/api-plugin-discounts-codes": "^1.2.0", - "@reactioncommerce/api-plugin-email": "^1.1.0", - "@reactioncommerce/api-plugin-email-smtp": "^1.0.5", - "@reactioncommerce/api-plugin-email-templates": "^1.1.1", - "@reactioncommerce/api-plugin-files": "^1.0.19", - "@reactioncommerce/api-plugin-i18n": "^1.0.4", - "@reactioncommerce/api-plugin-inventory": "^1.0.3", - "@reactioncommerce/api-plugin-inventory-simple": "^1.0.4", - "@reactioncommerce/api-plugin-job-queue": "^1.0.2", - "@reactioncommerce/api-plugin-navigation": "^1.1.1", - "@reactioncommerce/api-plugin-notifications": "^1.1.0", - "@reactioncommerce/api-plugin-orders": "^1.4.2", - "@reactioncommerce/api-plugin-payments": "^1.0.1", - "@reactioncommerce/api-plugin-payments-example": "^1.1.1", - "@reactioncommerce/api-plugin-payments-stripe-sca": "^1.0.2", - "@reactioncommerce/api-plugin-pricing-simple": "^1.0.3", - "@reactioncommerce/api-plugin-products": "^1.0.2", - "@reactioncommerce/api-plugin-settings": "^1.0.2", - "@reactioncommerce/api-plugin-fulfillment": "1.0.0", - "@reactioncommerce/api-plugin-fulfillment-type-shipping": "1.0.0", - "@reactioncommerce/api-plugin-fulfillment-method-shipping-flat-rate": "1.0.0", - "@reactioncommerce/api-plugin-fulfillment-method-shipping-dynamic-rate": "1.0.0", - "@reactioncommerce/api-plugin-fulfillment-type-pickup": "1.0.0", - "@reactioncommerce/api-plugin-fulfillment-method-pickup-store": "1.0.0", - "@reactioncommerce/api-plugin-shops": "^1.2.1", - "@reactioncommerce/api-plugin-simple-schema": "^1.0.3", - "@reactioncommerce/api-plugin-sitemap-generator": "^1.2.2", - "@reactioncommerce/api-plugin-surcharges": "^1.1.7", - "@reactioncommerce/api-plugin-system-information": "^1.1.3", - "@reactioncommerce/api-plugin-tags": "^1.1.1", - "@reactioncommerce/api-plugin-taxes": "^1.1.1", - "@reactioncommerce/api-plugin-taxes-flat-rate": "^1.0.4", - "@reactioncommerce/api-plugin-translations": "^1.2.0", - "@reactioncommerce/api-utils": "^1.16.5", - "@reactioncommerce/db-version-check": "^1.0.0", - "@reactioncommerce/file-collections": "^0.9.3", - "@reactioncommerce/file-collections-sa-gridfs": "^0.1.5", - "@reactioncommerce/logger": "^1.1.3", - "@reactioncommerce/nodemailer": "^5.0.5", - "@reactioncommerce/random": "^1.0.2", + "@reactioncommerce/api-core": "2.0.1", + "@reactioncommerce/api-plugin-accounts": "2.0.4", + "@reactioncommerce/api-plugin-address-validation": "1.3.3", + "@reactioncommerce/api-plugin-address-validation-test": "1.0.3", + "@reactioncommerce/api-plugin-authentication": "2.2.5", + "@reactioncommerce/api-plugin-authorization-simple": "1.3.2", + "@reactioncommerce/api-plugin-carts": "1.3.5", + "@reactioncommerce/api-plugin-catalogs": "1.1.2", + "@reactioncommerce/api-plugin-discounts": "1.0.4", + "@reactioncommerce/api-plugin-discounts-codes": "1.2.4", + "@reactioncommerce/api-plugin-email": "1.1.5", + "@reactioncommerce/api-plugin-email-smtp": "1.0.8", + "@reactioncommerce/api-plugin-email-templates": "1.1.7", + "@reactioncommerce/api-plugin-files": "1.0.19", + "@reactioncommerce/api-plugin-i18n": "1.0.6", + "@reactioncommerce/api-plugin-inventory": "1.0.5", + "@reactioncommerce/api-plugin-inventory-simple": "1.0.6", + "@reactioncommerce/api-plugin-job-queue": "1.0.7", + "@reactioncommerce/api-plugin-navigation": "1.1.2", + "@reactioncommerce/api-plugin-notifications": "1.1.4", + "@reactioncommerce/api-plugin-orders": "1.4.5", + "@reactioncommerce/api-plugin-payments": "1.0.5", + "@reactioncommerce/api-plugin-payments-example": "1.1.2", + "@reactioncommerce/api-plugin-payments-stripe-sca": "1.0.2", + "@reactioncommerce/api-plugin-pricing-simple": "1.0.7", + "@reactioncommerce/api-plugin-products": "1.3.1", + "@reactioncommerce/api-plugin-settings": "1.0.7", + "@reactioncommerce/api-plugin-shops": "1.3.0", + "@reactioncommerce/api-plugin-simple-schema": "1.0.3", + "@reactioncommerce/api-plugin-sitemap-generator": "1.2.3", + "@reactioncommerce/api-plugin-surcharges": "1.1.8", + "@reactioncommerce/api-plugin-system-information": "1.1.4", + "@reactioncommerce/api-plugin-tags": "1.2.0", + "@reactioncommerce/api-plugin-taxes": "1.1.3", + "@reactioncommerce/api-plugin-taxes-flat-rate": "1.0.8", + "@reactioncommerce/api-plugin-translations": "1.2.2", + "@reactioncommerce/api-utils": "1.17.0", + "@reactioncommerce/db-version-check": "1.0.0", + "@reactioncommerce/file-collections": "0.9.3", + "@reactioncommerce/file-collections-sa-gridfs": "0.1.5", + "@reactioncommerce/logger": "1.1.5", + "@reactioncommerce/nodemailer": "5.0.5", + "@reactioncommerce/random": "1.0.2", "@snyk/protect": "latest", "graphql": "~14.7.0", "semver": "~6.3.0", diff --git a/apps/reaction/plugins.json b/apps/reaction/plugins.json index 9d074892bc7..429aa5fd64e 100644 --- a/apps/reaction/plugins.json +++ b/apps/reaction/plugins.json @@ -28,12 +28,8 @@ "discounts": "@reactioncommerce/api-plugin-discounts", "discountCodes": "@reactioncommerce/api-plugin-discounts-codes", "surcharges": "@reactioncommerce/api-plugin-surcharges", - "fulfillment": "@reactioncommerce/api-plugin-fulfillment", - "fulfillmentTypeShipping": "@reactioncommerce/api-plugin-fulfillment-type-shipping", - "fulfillmentMethodShippingFlatRate": "@reactioncommerce/api-plugin-fulfillment-method-shipping-flat-rate", - "fulfillmentMethodShippingDynamicRate": "@reactioncommerce/api-plugin-fulfillment-method-shipping-dynamic-rate", - "fulfillmentTypePickup": "@reactioncommerce/api-plugin-fulfillment-type-pickup", - "fulfillmentMethodPickupStore": "@reactioncommerce/api-plugin-fulfillment-method-pickup-store", + "shipments": "@reactioncommerce/api-plugin-shipments", + "shipmentsFlatRate": "@reactioncommerce/api-plugin-shipments-flat-rate", "taxes": "@reactioncommerce/api-plugin-taxes", "taxesFlatRate": "@reactioncommerce/api-plugin-taxes-flat-rate", "navigation": "@reactioncommerce/api-plugin-navigation", diff --git a/packages/api-plugin-carts/src/mutations/setFulfillmentTypeForItems.js b/packages/api-plugin-carts/src/mutations/setFulfillmentTypeForItems.js index 612ad2c963f..64a2264b267 100644 --- a/packages/api-plugin-carts/src/mutations/setFulfillmentTypeForItems.js +++ b/packages/api-plugin-carts/src/mutations/setFulfillmentTypeForItems.js @@ -6,10 +6,10 @@ const inputSchema = new SimpleSchema({ "cartId": String, "cartToken": String, "fulfillmentType": String, - "items": { + "itemIds": { type: Array }, - "items.$": String + "itemIds.$": String }); /** @@ -27,7 +27,7 @@ export default async function setFulfillmentTypeForItems(context, input) { inputSchema.validate(input || {}); const { collections: { Cart } } = context; - const { cartId, cartToken, fulfillmentType, items: itemsInput } = input; + const { cartId, cartToken, fulfillmentType, itemIds: itemsInput } = input; const cart = await Cart.findOne({ _id: cartId, @@ -38,7 +38,7 @@ export default async function setFulfillmentTypeForItems(context, input) { if (!fulfillmentType || fulfillmentType === "undecided") throw new ReactionError("invalid-param", "Invalid Fulfillment Type received"); - if (!itemsInput || itemsInput.length === 0) throw new ReactionError("invalid-param", "Items not provided"); + if (!itemsInput || itemsInput.length === 0) throw new ReactionError("invalid-param", "Item Ids not provided"); cart.items = (cart.items || []).map((item) => { if (itemsInput.includes(item._id)) { diff --git a/packages/api-plugin-carts/src/mutations/setFulfillmentTypeForItems.test.js b/packages/api-plugin-carts/src/mutations/setFulfillmentTypeForItems.test.js index 43420c8700d..73c6f971c94 100644 --- a/packages/api-plugin-carts/src/mutations/setFulfillmentTypeForItems.test.js +++ b/packages/api-plugin-carts/src/mutations/setFulfillmentTypeForItems.test.js @@ -48,7 +48,7 @@ test("throws if cart to be updated not found", async () => { cartId: "cartId", cartToken, fulfillmentType: "shipping", - items: ["cartItemId"] + itemIds: ["cartItemId"] })).rejects.toThrow(expectedError); }); @@ -59,7 +59,7 @@ test("throws if invalid fulfillment type provided", async () => { cartId: "cartId", cartToken, fulfillmentType: "undecided", - items: ["cartItemId"] + itemIds: ["cartItemId"] })).rejects.toThrow(expectedError); }); @@ -70,7 +70,7 @@ test("throws if invalid fulfillment type provided", async () => { cartId: "cartId", cartToken, fulfillmentType: "shipping", - items: [] + itemIds: [] })).rejects.toThrow(expectedError); }); @@ -81,7 +81,7 @@ test("sets the selected fulfillment type for each input group in the cart", asyn cartId: "cartId", cartToken, fulfillmentType: "shipping", - items: ["cartItemId2"] + itemIds: ["cartItemId2"] }); const expected = { diff --git a/packages/api-plugin-carts/src/resolvers/Mutation/setFulfillmentTypeForItems.js b/packages/api-plugin-carts/src/resolvers/Mutation/setFulfillmentTypeForItems.js index dc7daa5718d..e52a126c029 100644 --- a/packages/api-plugin-carts/src/resolvers/Mutation/setFulfillmentTypeForItems.js +++ b/packages/api-plugin-carts/src/resolvers/Mutation/setFulfillmentTypeForItems.js @@ -10,22 +10,22 @@ import { decodeCartItemOpaqueId, decodeCartOpaqueId } from "../../xforms/id.js"; * @param {String} args.input.cartId - The opaque ID of the cart to add the items to. * @param {String} args.input.cartToken - The anonymous access cartToken that was returned from `createCart`. * @param {String} args.input.fulfillmentType - The fulfillment type to be set fo all items. - * @param {String} [args.input.items] - An array of cart items to be assigned to the fulfillment type. + * @param {String} [args.input.itemIds] - An array of cart items to be assigned to the fulfillment type. * @param {String} args.input.clientMutationId - An optional string identifying the mutation call * @param {Object} context - an object containing the per-request state * @returns {Promise} setFulfillmentTypeForItemsPayload */ export default async function setFulfillmentTypeForItems(parentResult, { input }, context) { - const { cartId: opaqueCartId, clientMutationId = null, fulfillmentType, items: itemsInput, cartToken } = input; + const { cartId: opaqueCartId, clientMutationId = null, fulfillmentType, itemIds: itemsInput, cartToken } = input; const cartId = decodeCartOpaqueId(opaqueCartId); - const items = itemsInput.map((item) => (decodeCartItemOpaqueId(item))); + const itemIds = itemsInput.map((item) => (decodeCartItemOpaqueId(item))); const { cart } = await context.mutations.setFulfillmentTypeForItems(context, { cartId, fulfillmentType, - items, + itemIds, cartToken }); diff --git a/packages/api-plugin-carts/src/schemas/cart.graphql b/packages/api-plugin-carts/src/schemas/cart.graphql index dfeacf89ddc..741a56b503a 100644 --- a/packages/api-plugin-carts/src/schemas/cart.graphql +++ b/packages/api-plugin-carts/src/schemas/cart.graphql @@ -367,7 +367,7 @@ input SetFulfillmentTypeForItemsInput { fulfillmentType: String! "Array of item IDs for which selectedFulfillmentType has to be updated" - items: [String]! + itemIds: [String]! } "Input for the `removeCartItems` mutation" diff --git a/packages/api-plugin-carts/src/util/updateCartFulfillmentGroups.js b/packages/api-plugin-carts/src/util/updateCartFulfillmentGroups.js index 2f902503330..a423823c376 100644 --- a/packages/api-plugin-carts/src/util/updateCartFulfillmentGroups.js +++ b/packages/api-plugin-carts/src/util/updateCartFulfillmentGroups.js @@ -1,3 +1,4 @@ +import _ from "lodash"; import Random from "@reactioncommerce/random"; import ReactionError from "@reactioncommerce/reaction-error"; @@ -10,14 +11,12 @@ import ReactionError from "@reactioncommerce/reaction-error"; */ function determineRemoveFromGroups(currentGroups, itemId, shopId) { let removeFromGroups = currentGroups.map((group) => { - if (group.itemIds && group.itemIds.includes(itemId) && shopId === group.shopId) { + if (group.itemIds?.includes?.(itemId) && shopId === group.shopId) { return group; } return null; }); - removeFromGroups = removeFromGroups.filter((group) => !!group); // remove nulls - // If the item is present in more that one Fulfillment group, we remove it from all(for backward compatibility) - return removeFromGroups || null; + return _.compact(removeFromGroups); } /** @@ -41,13 +40,10 @@ function determineAddToGroup(currentGroups, selectedFulfillmentType, shopId) { * @returns {undefined} */ function checkAndAddToGroup(currentGroups, fulfillmentType, item) { - let removeFromGroups = determineRemoveFromGroups(currentGroups, item._id, item.shopId); - if (removeFromGroups && removeFromGroups.length > 0) { - removeFromGroups = (removeFromGroups || []).map((group) => { - group.itemIds = (group.itemIds || []).filter((itemId) => item._id !== itemId); - return group; - }); - } + const removeFromGroups = determineRemoveFromGroups(currentGroups, item._id, item.shopId); + removeFromGroups.forEach((group) => { + group.itemIds = _.pull(group.itemIds, item._id); + }); const addToGroup = determineAddToGroup(currentGroups, fulfillmentType, item.shopId); if (!addToGroup) { @@ -58,12 +54,9 @@ function checkAndAddToGroup(currentGroups, fulfillmentType, item) { shopId: item.shopId, type: fulfillmentType }); - } else if (!addToGroup.itemIds) { - // If there is a compatible group but it has no items array, add one with just this item in it - addToGroup.itemIds = [item._id]; - } else if (!addToGroup.itemIds.includes(item._id)) { - // If there is a compatible group with an items array but it is missing this item, add this item ID to the array - addToGroup.itemIds.push(item._id); + } else { + // If compatible group, add this item to itemIds array of that group if not already present + addToGroup.itemIds = _.uniq([...addToGroup.itemIds || [], item._id]); } } @@ -95,26 +88,27 @@ export default async function updateCartFulfillmentGroups(context, cart) { } } - if (selectedFulfillmentType && !supportedFulfillmentTypes.includes(selectedFulfillmentType)) { - throw new ReactionError("not-found", "Selected fulfillmentType is not supported by the Product"); - } - - // When selectedFulfillmentType is not available, if the product only supports ONE fulfillment type, use that - // If more than one fulfillment type is available, then add item to undecided group - if (!selectedFulfillmentType) { - if (supportedFulfillmentTypes.length === 1) { - ([selectedFulfillmentType] = supportedFulfillmentTypes); - } else { - selectedFulfillmentType = "undecided"; + const normalizeSelectedFulfillmentType = (selectedFulfillmentType) => { + // When selectedFulfillmentType is not available, if the product only supports ONE fulfillment type, use that + // If more than one fulfillment type is available, then add item to undecided group + if (_.isNil(selectedFulfillmentType)) { + const hasOnlyOneSupportedFulfillmentType = supportedFulfillmentTypes.length === 1; + if (hasOnlyOneSupportedFulfillmentType) return _.first(supportedFulfillmentTypes); + return "undecided"; } - } + if(!supportedFulfillmentTypes.includes(selectedFulfillmentType)) { + throw new ReactionError("not-found", "Selected fulfillmentType is not supported by the Product"); + } + return selectedFulfillmentType; + }; + + selectedFulfillmentType = normalizeSelectedFulfillmentType(selectedFulfillmentType); + // check if the selectedFulfillmentType is an 'enabled' fulfillmentType, if not set is 'undecided' /* eslint-disable no-await-in-loop */ - const enabledFulfillmentTypeObjs = await Fulfillment.find({ "shopId": item.shopId, "provider.enabled": true }).toArray(); + const enabledFulfillmentType = await Fulfillment.findOne({ "shopId": item.shopId, "provider.enabled": true, "fulfillmentType": selectedFulfillmentType }); /* eslint-enable no-await-in-loop */ - let enabledFulfillmentTypes = (enabledFulfillmentTypeObjs || []).map((ffType) => ffType.fulfillmentType); - enabledFulfillmentTypes = (enabledFulfillmentTypes || []).filter((val) => !!val); // Remove nulls - if (!enabledFulfillmentTypes.includes(selectedFulfillmentType)) selectedFulfillmentType = "undecided"; + if (!enabledFulfillmentType) selectedFulfillmentType = "undecided"; checkAndAddToGroup(currentGroups, selectedFulfillmentType, item); } From 61bc0a3bfb4b42b7dc052eb3511536a6f3cf2bbd Mon Sep 17 00:00:00 2001 From: Sujith Date: Sat, 11 Mar 2023 10:38:51 +0530 Subject: [PATCH 60/71] fix: unit test update Signed-off-by: Sujith --- .../src/mutations/setFulfillmentTypeForItems.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/api-plugin-carts/src/mutations/setFulfillmentTypeForItems.test.js b/packages/api-plugin-carts/src/mutations/setFulfillmentTypeForItems.test.js index 73c6f971c94..f4f78c798f6 100644 --- a/packages/api-plugin-carts/src/mutations/setFulfillmentTypeForItems.test.js +++ b/packages/api-plugin-carts/src/mutations/setFulfillmentTypeForItems.test.js @@ -65,7 +65,7 @@ test("throws if invalid fulfillment type provided", async () => { test("throws if invalid fulfillment type provided", async () => { mockContext.collections.Cart.findOne.mockReturnValueOnce(Promise.resolve(dbCart)); - const expectedError = new ReactionError("invalid-param", "Items not provided"); + const expectedError = new ReactionError("invalid-param", "Item Ids not provided"); await expect(setFulfillmentTypeForItems(mockContext, { cartId: "cartId", cartToken, From 3e6b7d28857d380f8acea790cb06c8c68335620b Mon Sep 17 00:00:00 2001 From: Sujith Date: Sun, 12 Mar 2023 11:40:02 +0530 Subject: [PATCH 61/71] fix: variable reuse Signed-off-by: Sujith --- .../src/util/updateCartFulfillmentGroups.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/api-plugin-carts/src/util/updateCartFulfillmentGroups.js b/packages/api-plugin-carts/src/util/updateCartFulfillmentGroups.js index a423823c376..9926fe99578 100644 --- a/packages/api-plugin-carts/src/util/updateCartFulfillmentGroups.js +++ b/packages/api-plugin-carts/src/util/updateCartFulfillmentGroups.js @@ -10,7 +10,7 @@ import ReactionError from "@reactioncommerce/reaction-error"; * @returns {Object[]|null} The groups array or null if no viable group */ function determineRemoveFromGroups(currentGroups, itemId, shopId) { - let removeFromGroups = currentGroups.map((group) => { + const removeFromGroups = currentGroups.map((group) => { if (group.itemIds?.includes?.(itemId) && shopId === group.shopId) { return group; } @@ -88,18 +88,18 @@ export default async function updateCartFulfillmentGroups(context, cart) { } } - const normalizeSelectedFulfillmentType = (selectedFulfillmentType) => { - // When selectedFulfillmentType is not available, if the product only supports ONE fulfillment type, use that + const normalizeSelectedFulfillmentType = (selectedFulfillmentTypeInp) => { + // When selectedFulfillmentTypeInp is not available, if the product only supports ONE fulfillment type, use that // If more than one fulfillment type is available, then add item to undecided group - if (_.isNil(selectedFulfillmentType)) { + if (_.isNil(selectedFulfillmentTypeInp)) { const hasOnlyOneSupportedFulfillmentType = supportedFulfillmentTypes.length === 1; if (hasOnlyOneSupportedFulfillmentType) return _.first(supportedFulfillmentTypes); return "undecided"; } - if(!supportedFulfillmentTypes.includes(selectedFulfillmentType)) { + if (!supportedFulfillmentTypes.includes(selectedFulfillmentTypeInp)) { throw new ReactionError("not-found", "Selected fulfillmentType is not supported by the Product"); } - return selectedFulfillmentType; + return selectedFulfillmentTypeInp; }; selectedFulfillmentType = normalizeSelectedFulfillmentType(selectedFulfillmentType); From a1cfcd79949ab872ae3d0ab3dbb8d2d7df7ab23e Mon Sep 17 00:00:00 2001 From: Sujith Date: Mon, 13 Mar 2023 13:28:20 +0530 Subject: [PATCH 62/71] fix: integration test updates Signed-off-by: Sujith --- apps/reaction/jest.config.cjs | 8 ++- apps/reaction/package.json | 6 +++ apps/reaction/plugins.json | 8 ++- .../addOrderFulfillmentGroup.test.js | 2 +- .../moveOrderItems/moveOrderItems.test.js | 2 +- .../splitOrderItem/splitOrderItem.test.js | 2 +- apps/reaction/tests/util/factory.js | 9 ++-- .../src/queries/getCartById.js | 17 ++++++ .../api-plugin-carts/src/queries/index.js | 2 + .../src/util/getCartById.test.js | 46 ++++++++++++++++ .../createFlatRateFulfillmentMethod.js | 1 + .../updateFlatRateFulfillmentMethod.js | 1 + .../src/schemas/schema.graphql | 4 +- .../src/simpleSchemas.js | 5 +- .../src/json-data/ShippingMethod.json | 3 +- pnpm-lock.yaml | 52 +++++++++++-------- 16 files changed, 131 insertions(+), 37 deletions(-) create mode 100644 packages/api-plugin-carts/src/queries/getCartById.js create mode 100644 packages/api-plugin-carts/src/util/getCartById.test.js diff --git a/apps/reaction/jest.config.cjs b/apps/reaction/jest.config.cjs index 212011cff73..d16f2a477fa 100644 --- a/apps/reaction/jest.config.cjs +++ b/apps/reaction/jest.config.cjs @@ -30,8 +30,12 @@ const externalNodeModules = [ "@reactioncommerce/api-plugin-pricing-simple", "@reactioncommerce/api-plugin-products", "@reactioncommerce/api-plugin-settings", - "@reactioncommerce/api-plugin-shipments-flat-rate", - "@reactioncommerce/api-plugin-shipments", + "@reactioncommerce/api-plugin-fulfillment", + "@reactioncommerce/api-plugin-fulfillment-type-shipping", + "@reactioncommerce/api-plugin-fulfillment-type-pickup", + "@reactioncommerce/api-plugin-fulfillment-method-shipping-flat-rate", + "@reactioncommerce/api-plugin-fulfillment-method-shipping-dynamic-rate", + "@reactioncommerce/api-plugin-fulfillment-method-pickup-store", "@reactioncommerce/api-plugin-shops", "@reactioncommerce/api-plugin-simple-schema", "@reactioncommerce/api-plugin-sitemap-generator", diff --git a/apps/reaction/package.json b/apps/reaction/package.json index d658c3f81dd..1a07f7c1a59 100644 --- a/apps/reaction/package.json +++ b/apps/reaction/package.json @@ -37,6 +37,12 @@ "@reactioncommerce/api-plugin-email-smtp": "1.0.8", "@reactioncommerce/api-plugin-email-templates": "1.1.7", "@reactioncommerce/api-plugin-files": "1.1.0", + "@reactioncommerce/api-plugin-fulfillment": "1.0.0", + "@reactioncommerce/api-plugin-fulfillment-type-shipping": "1.0.0", + "@reactioncommerce/api-plugin-fulfillment-type-pickup": "1.0.0", + "@reactioncommerce/api-plugin-fulfillment-method-shipping-flat-rate": "1.0.0", + "@reactioncommerce/api-plugin-fulfillment-method-shipping-dynamic-rate": "1.0.0", + "@reactioncommerce/api-plugin-fulfillment-method-pickup-store": "1.0.0", "@reactioncommerce/api-plugin-i18n": "1.0.6", "@reactioncommerce/api-plugin-inventory": "1.0.5", "@reactioncommerce/api-plugin-inventory-simple": "1.0.6", diff --git a/apps/reaction/plugins.json b/apps/reaction/plugins.json index 429aa5fd64e..c9502222931 100644 --- a/apps/reaction/plugins.json +++ b/apps/reaction/plugins.json @@ -28,8 +28,12 @@ "discounts": "@reactioncommerce/api-plugin-discounts", "discountCodes": "@reactioncommerce/api-plugin-discounts-codes", "surcharges": "@reactioncommerce/api-plugin-surcharges", - "shipments": "@reactioncommerce/api-plugin-shipments", - "shipmentsFlatRate": "@reactioncommerce/api-plugin-shipments-flat-rate", + "fulfillment": "@reactioncommerce/api-plugin-fulfillment", + "fulfillmentTypeShipping": "@reactioncommerce/api-plugin-fulfillment-type-shipping", + "fulfillmentTypePickup": "@reactioncommerce/api-plugin-fulfillment-type-pickup", + "fulfillmentMethodShippingFlatRate": "@reactioncommerce/api-plugin-fulfillment-method-shipping-flat-rate", + "fulfillmentMethodShippingDynamicRate": "@reactioncommerce/api-plugin-fulfillment-method-shipping-dynamic-rate", + "fulfillmentMethodPickupStore": "@reactioncommerce/api-plugin-fulfillment-method-pickup-store", "taxes": "@reactioncommerce/api-plugin-taxes", "taxesFlatRate": "@reactioncommerce/api-plugin-taxes-flat-rate", "navigation": "@reactioncommerce/api-plugin-navigation", diff --git a/apps/reaction/tests/integration/api/mutations/addOrderFulfillmentGroup/addOrderFulfillmentGroup.test.js b/apps/reaction/tests/integration/api/mutations/addOrderFulfillmentGroup/addOrderFulfillmentGroup.test.js index c96764b2e5e..d2351f44bc6 100644 --- a/apps/reaction/tests/integration/api/mutations/addOrderFulfillmentGroup/addOrderFulfillmentGroup.test.js +++ b/apps/reaction/tests/integration/api/mutations/addOrderFulfillmentGroup/addOrderFulfillmentGroup.test.js @@ -86,7 +86,7 @@ beforeAll(async () => { testApp.registerPlugin({ name: "addOrderFulfillmentGroup.test.js", functionsByType: { - getFulfillmentMethodsWithQuotesShipping: [getFulfillmentMethodsWithQuotes] + fulfillmentMethodsWithQuotes: [{ key: "shipping", handler: getFulfillmentMethodsWithQuotes }] } }); diff --git a/apps/reaction/tests/integration/api/mutations/moveOrderItems/moveOrderItems.test.js b/apps/reaction/tests/integration/api/mutations/moveOrderItems/moveOrderItems.test.js index 3bb563a302d..c95ac72fcf6 100644 --- a/apps/reaction/tests/integration/api/mutations/moveOrderItems/moveOrderItems.test.js +++ b/apps/reaction/tests/integration/api/mutations/moveOrderItems/moveOrderItems.test.js @@ -74,7 +74,7 @@ beforeAll(async () => { testApp.registerPlugin({ name: "moveOrderItems.test.js", functionsByType: { - getFulfillmentMethodsWithQuotesShipping: [getFulfillmentMethodsWithQuotes] + fulfillmentMethodsWithQuotes: [{ key: "shipping", handler: getFulfillmentMethodsWithQuotes }] } }); diff --git a/apps/reaction/tests/integration/api/mutations/splitOrderItem/splitOrderItem.test.js b/apps/reaction/tests/integration/api/mutations/splitOrderItem/splitOrderItem.test.js index 5f06f3b7cce..21f8cd40aa1 100644 --- a/apps/reaction/tests/integration/api/mutations/splitOrderItem/splitOrderItem.test.js +++ b/apps/reaction/tests/integration/api/mutations/splitOrderItem/splitOrderItem.test.js @@ -82,7 +82,7 @@ beforeAll(async () => { testApp.registerPlugin({ name: "splitOrderItem.test.js", functionsByType: { - getFulfillmentMethodsWithQuotesShipping: [getFulfillmentMethodsWithQuotes] + fulfillmentMethodsWithQuotes: [{ key: "shipping", handler: getFulfillmentMethodsWithQuotes }] } }); diff --git a/apps/reaction/tests/util/factory.js b/apps/reaction/tests/util/factory.js index 1f8f64ca382..f68483910ba 100644 --- a/apps/reaction/tests/util/factory.js +++ b/apps/reaction/tests/util/factory.js @@ -79,9 +79,10 @@ import { Shop } from "@reactioncommerce/api-plugin-shops/src/simpleSchemas.js"; -import FulfillmentMethod from "@reactioncommerce/api-plugin-shipments-flat-rate/src/util/methodSchema.js"; - -import Restriction from "@reactioncommerce/api-plugin-shipments-flat-rate/src/util/restrictionSchema.js"; +import { + methodSchema as FulfillmentMethod, + restrictionSchema as Restriction +} from "@reactioncommerce/api-plugin-fulfillment-method-shipping-flat-rate/src/simpleSchemas.js"; import { Sitemap @@ -108,7 +109,7 @@ import { FulfillmentMethodSchema, fulfillmentTypeSchema, extendFulfillmentSchemas -} from "../../../../packages/api-plugin-fulfillment/src/simpleSchemas.js"; +} from "@reactioncommerce/api-plugin-fulfillment/src/simpleSchemas.js"; const schemasToAddToFactory = { MethodEmptyData, diff --git a/packages/api-plugin-carts/src/queries/getCartById.js b/packages/api-plugin-carts/src/queries/getCartById.js new file mode 100644 index 00000000000..845e7793438 --- /dev/null +++ b/packages/api-plugin-carts/src/queries/getCartById.js @@ -0,0 +1,17 @@ +import getCartByIdUtil from "../util/getCartById.js"; +/** + * @name getCartById + * @method + * @memberof Cart + * @summary Gets a cart from the db by ID. If there is an account for the request, verifies that the + * account has permission to access the cart. Optionally throws an error if not found. + * @param {Object} context - an object containing the per-request state + * @param {String} cartId The cart ID + * @param {Object} [options] Options + * @param {String} [options.cartToken] Cart token, required if it's an anonymous cart + * @param {Boolean} [options.throwIfNotFound] Default false. Throw a not-found error rather than return null `cart` + * @returns {Object|null} The cart document, or null if not found and `throwIfNotFound` was false + */ +export default async function getCartById(context, cartId, { cartToken, throwIfNotFound = false } = {}) { + return getCartByIdUtil(context, cartId, { cartToken, throwIfNotFound }); +} diff --git a/packages/api-plugin-carts/src/queries/index.js b/packages/api-plugin-carts/src/queries/index.js index 4c77c918431..7693c62f8ab 100644 --- a/packages/api-plugin-carts/src/queries/index.js +++ b/packages/api-plugin-carts/src/queries/index.js @@ -1,9 +1,11 @@ import accountCartByAccountId from "./accountCartByAccountId.js"; import anonymousCartByCartId from "./anonymousCartByCartId.js"; +import getCartById from "./getCartById.js"; import getCommonOrderForCartGroup from "./getCommonOrderForCartGroup.js"; export default { accountCartByAccountId, anonymousCartByCartId, + getCartById, getCommonOrderForCartGroup }; diff --git a/packages/api-plugin-carts/src/util/getCartById.test.js b/packages/api-plugin-carts/src/util/getCartById.test.js new file mode 100644 index 00000000000..a2ffdeda808 --- /dev/null +++ b/packages/api-plugin-carts/src/util/getCartById.test.js @@ -0,0 +1,46 @@ +import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; +import ReactionError from "@reactioncommerce/reaction-error"; +import getCartById from "./getCartById.js"; + +test("should throw when cart not found and throwIfNotFound is true", async () => { + const cartId = "123"; + const cartToken = "xyz"; + mockContext.collections.Cart.findOne.mockReturnValueOnce(null); + const expectedError = new ReactionError("not-found", "Cart not found"); + await expect(getCartById(mockContext, cartId, { cartToken, throwIfNotFound: true })).rejects.toThrow(expectedError); +}); + +test("should return null when cart not found and throwIfNotFound is false", async () => { + const cartId = "123"; + const cartToken = "xyz"; + mockContext.collections.Cart.findOne.mockReturnValueOnce(null); + await expect(getCartById(mockContext, cartId, { cartToken, throwIfNotFound: false })).toMatchObject({}); +}); + +test("should throw when cart found but accountId does not match", async () => { + const cartId = "123"; + const cartToken = "xyz"; + mockContext.accountId = "accountId123"; + const cart = { + _id: cartId, + anonymousAccessToken: cartToken, + accountId: "accountId456" + }; + mockContext.collections.Cart.findOne.mockReturnValueOnce(Promise.resolve(cart)); + const expectedError = new ReactionError("access-denied", "Access Denied"); + await expect(getCartById(mockContext, cartId, { cartToken, throwIfNotFound: true })).rejects.toThrow(expectedError); +}); + +test("should return cart when cart found and accountId matches", async () => { + const cartId = "123"; + const cartToken = "xyz"; + mockContext.accountId = "accountId123"; + const cart = { + _id: cartId, + anonymousAccessToken: cartToken, + accountId: "accountId123" + }; + mockContext.collections.Cart.findOne.mockReturnValueOnce(Promise.resolve(cart)); + const result = await getCartById(mockContext, cartId, { cartToken, throwIfNotFound: true }); + expect(result).toMatchObject(cart); +}); diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/createFlatRateFulfillmentMethod.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/createFlatRateFulfillmentMethod.js index 1f35dcdc095..17d7ebca271 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/createFlatRateFulfillmentMethod.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/createFlatRateFulfillmentMethod.js @@ -41,6 +41,7 @@ export default async function createFlatRateFulfillmentMethod(context, input) { method._id = Random.id(); // `isEnabled` has been marked @deprecated and will be removed in next release. 'enabled' is the replacement field + if (!method.enabled && !method.isEnabled) method.enabled = false; if (!method.enabled) method.enabled = method.isEnabled; // if user not yet using new field, continue to collect it from old field if (method.isEnabled) delete method.isEnabled; diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentMethod.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentMethod.js index 8fd38f38b3f..8c8db725411 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentMethod.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/mutations/updateFlatRateFulfillmentMethod.js @@ -39,6 +39,7 @@ export default async function updateFlatRateFulfillmentMethod(context, input) { await context.validatePermissions("reaction:legacy:fulfillmentMethods", "update", { shopId }); // `isEnabled` has been marked @deprecated and will be removed in next release. 'enabled' is the replacement field + if (!method.enabled && !method.isEnabled) method.enabled = false; if (!method.enabled) method.enabled = method.isEnabled; // if user not yet using new field, continue to collect it from old field if (method.isEnabled) delete method.isEnabled; diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/schemas/schema.graphql b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/schemas/schema.graphql index fda1ed20187..38bb4622c36 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/schemas/schema.graphql +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/schemas/schema.graphql @@ -83,7 +83,7 @@ type FlatRateFulfillmentMethod implements Node { handling: Float! "Include this as a fulfillment option shown to shoppers during checkout?" - isEnabled: Boolean! @deprecated(reason: "Use `enabled`") + isEnabled: Boolean @deprecated(reason: "Use `enabled`") "Replacement field for the deprecated `isEnabled`, matches with the database definition" enabled: Boolean @@ -125,7 +125,7 @@ input FlatRateFulfillmentMethodInput { handling: Float! "Include this as a fulfillment option shown to shoppers during checkout?" - isEnabled: Boolean! @deprecated(reason: "Use `enabled`") + isEnabled: Boolean @deprecated(reason: "Use `enabled`") "Replacement field for the deprecated `isEnabled`, matches with the database definition" enabled: Boolean diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/simpleSchemas.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/simpleSchemas.js index c705b49482e..7f288986ee6 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/simpleSchemas.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/simpleSchemas.js @@ -28,7 +28,10 @@ export const methodSchema = new SimpleSchema({ "fulfillmentTypes.$": String, "group": String, "handling": Number, - "isEnabled": Boolean, + "isEnabled": { + type: Boolean, + optional: true + }, "enabled": { type: Boolean, optional: true diff --git a/packages/api-plugin-sample-data/src/json-data/ShippingMethod.json b/packages/api-plugin-sample-data/src/json-data/ShippingMethod.json index 3a130cd6464..006626ccbf6 100644 --- a/packages/api-plugin-sample-data/src/json-data/ShippingMethod.json +++ b/packages/api-plugin-sample-data/src/json-data/ShippingMethod.json @@ -7,5 +7,6 @@ "cost": 5, "handling": 2, "rate": 9, - "isEnabled": true + "isEnabled": true, + "enabled": true } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4d45ff228c5..2e8ae03c278 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -156,6 +156,12 @@ importers: '@reactioncommerce/api-plugin-email-smtp': 1.0.8 '@reactioncommerce/api-plugin-email-templates': 1.1.7 '@reactioncommerce/api-plugin-files': 1.1.0 + '@reactioncommerce/api-plugin-fulfillment': 1.0.0 + '@reactioncommerce/api-plugin-fulfillment-method-pickup-store': 1.0.0 + '@reactioncommerce/api-plugin-fulfillment-method-shipping-dynamic-rate': 1.0.0 + '@reactioncommerce/api-plugin-fulfillment-method-shipping-flat-rate': 1.0.0 + '@reactioncommerce/api-plugin-fulfillment-type-pickup': 1.0.0 + '@reactioncommerce/api-plugin-fulfillment-type-shipping': 1.0.0 '@reactioncommerce/api-plugin-i18n': 1.0.6 '@reactioncommerce/api-plugin-inventory': 1.0.5 '@reactioncommerce/api-plugin-inventory-simple': 1.0.6 @@ -169,8 +175,6 @@ importers: '@reactioncommerce/api-plugin-pricing-simple': 1.0.7 '@reactioncommerce/api-plugin-products': 1.3.1 '@reactioncommerce/api-plugin-settings': 1.0.7 - '@reactioncommerce/api-plugin-shipments': 1.0.3 - '@reactioncommerce/api-plugin-shipments-flat-rate': 1.0.10 '@reactioncommerce/api-plugin-shops': 1.3.0 '@reactioncommerce/api-plugin-simple-schema': 1.0.3 '@reactioncommerce/api-plugin-sitemap-generator': 1.2.3 @@ -216,6 +220,12 @@ importers: '@reactioncommerce/api-plugin-email-smtp': link:../../packages/api-plugin-email-smtp '@reactioncommerce/api-plugin-email-templates': link:../../packages/api-plugin-email-templates '@reactioncommerce/api-plugin-files': link:../../packages/api-plugin-files + '@reactioncommerce/api-plugin-fulfillment': link:../../packages/api-plugin-fulfillment + '@reactioncommerce/api-plugin-fulfillment-method-pickup-store': link:../../packages/api-plugin-fulfillment-method-pickup-store + '@reactioncommerce/api-plugin-fulfillment-method-shipping-dynamic-rate': link:../../packages/api-plugin-fulfillment-method-shipping-dynamic-rate + '@reactioncommerce/api-plugin-fulfillment-method-shipping-flat-rate': link:../../packages/api-plugin-fulfillment-method-shipping-flat-rate + '@reactioncommerce/api-plugin-fulfillment-type-pickup': link:../../packages/api-plugin-fulfillment-type-pickup + '@reactioncommerce/api-plugin-fulfillment-type-shipping': link:../../packages/api-plugin-fulfillment-type-shipping '@reactioncommerce/api-plugin-i18n': link:../../packages/api-plugin-i18n '@reactioncommerce/api-plugin-inventory': link:../../packages/api-plugin-inventory '@reactioncommerce/api-plugin-inventory-simple': link:../../packages/api-plugin-inventory-simple @@ -229,8 +239,6 @@ importers: '@reactioncommerce/api-plugin-pricing-simple': link:../../packages/api-plugin-pricing-simple '@reactioncommerce/api-plugin-products': link:../../packages/api-plugin-products '@reactioncommerce/api-plugin-settings': link:../../packages/api-plugin-settings - '@reactioncommerce/api-plugin-shipments': link:../../packages/api-plugin-shipments - '@reactioncommerce/api-plugin-shipments-flat-rate': link:../../packages/api-plugin-shipments-flat-rate '@reactioncommerce/api-plugin-shops': link:../../packages/api-plugin-shops '@reactioncommerce/api-plugin-simple-schema': link:../../packages/api-plugin-simple-schema '@reactioncommerce/api-plugin-sitemap-generator': link:../../packages/api-plugin-sitemap-generator @@ -705,39 +713,35 @@ importers: '@reactioncommerce/api-plugin-carts': link:../api-plugin-carts '@reactioncommerce/data-factory': 1.0.1 - packages/api-plugin-fulfillment-method-shipping-dynamic-rate: + packages/api-plugin-fulfillment-method-pickup-store: specifiers: '@reactioncommerce/api-utils': ^1.16.9 + '@reactioncommerce/logger': ^1.1.4 '@reactioncommerce/reaction-error': ^1.0.1 simpl-schema: ^1.12.2 dependencies: '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/logger': link:../logger '@reactioncommerce/reaction-error': link:../reaction-error simpl-schema: 1.12.3 - packages/api-plugin-fulfillment-method-shipping-flat-rate: + packages/api-plugin-fulfillment-method-shipping-dynamic-rate: specifiers: '@reactioncommerce/api-utils': ^1.16.9 - '@reactioncommerce/logger': ^1.1.3 - '@reactioncommerce/random': ~1.0.2 '@reactioncommerce/reaction-error': ^1.0.1 - lodash: ^4.17.15 simpl-schema: ^1.12.2 dependencies: '@reactioncommerce/api-utils': link:../api-utils - '@reactioncommerce/logger': link:../logger - '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error - lodash: 4.17.21 simpl-schema: 1.12.3 - packages/api-plugin-fulfillment-type-shipping: + packages/api-plugin-fulfillment-method-shipping-flat-rate: specifiers: '@reactioncommerce/api-utils': ^1.16.9 '@reactioncommerce/logger': ^1.1.3 '@reactioncommerce/random': ~1.0.2 '@reactioncommerce/reaction-error': ^1.0.1 - lodash: ^4.17.21 + lodash: ^4.17.15 simpl-schema: ^1.12.2 dependencies: '@reactioncommerce/api-utils': link:../api-utils @@ -747,27 +751,31 @@ importers: lodash: 4.17.21 simpl-schema: 1.12.3 - packages/api-plugin-fulfillment-method-pickup-store: + packages/api-plugin-fulfillment-type-pickup: specifiers: '@reactioncommerce/api-utils': ^1.16.9 - '@reactioncommerce/logger': ^1.1.4 - '@reactioncommerce/reaction-error': ^1.0.1 - simpl-schema: ^1.12.2 + '@reactioncommerce/random': ~1.0.2 + '@reactioncommerce/reaction-error': 1.0.1 dependencies: '@reactioncommerce/api-utils': link:../api-utils - '@reactioncommerce/logger': link:../logger + '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error - simpl-schema: 1.12.3 - packages/api-plugin-fulfillment-type-pickup: + packages/api-plugin-fulfillment-type-shipping: specifiers: '@reactioncommerce/api-utils': ^1.16.9 + '@reactioncommerce/logger': ^1.1.3 '@reactioncommerce/random': ~1.0.2 - '@reactioncommerce/reaction-error': 1.0.1 + '@reactioncommerce/reaction-error': ^1.0.1 + lodash: ^4.17.21 + simpl-schema: ^1.12.2 dependencies: '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error + lodash: 4.17.21 + simpl-schema: 1.12.3 packages/api-plugin-i18n: specifiers: From 1d94aa6f8bb7fe4ae75d82fef8682ce46c63d041 Mon Sep 17 00:00:00 2001 From: Sujith Date: Mon, 13 Mar 2023 14:21:45 +0530 Subject: [PATCH 63/71] fix: filename case change Signed-off-by: Sujith --- ...ateOrderMethodsflatrate.js => validateOrderMethodsFlatRate.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/{validateOrderMethodsflatrate.js => validateOrderMethodsFlatRate.js} (100%) diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/validateOrderMethodsflatrate.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/validateOrderMethodsFlatRate.js similarity index 100% rename from packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/validateOrderMethodsflatrate.js rename to packages/api-plugin-fulfillment-method-shipping-flat-rate/src/util/validateOrderMethodsFlatRate.js From 059e4f057e395fe4e42f38dffb43842b142c4cc5 Mon Sep 17 00:00:00 2001 From: Sujith Date: Mon, 13 Mar 2023 14:29:06 +0530 Subject: [PATCH 64/71] fix: filename case 2 Signed-off-by: Sujith --- ...{validateOrderMethodsstore.js => validateOrderMethodsStore.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/api-plugin-fulfillment-method-pickup-store/src/util/{validateOrderMethodsstore.js => validateOrderMethodsStore.js} (100%) diff --git a/packages/api-plugin-fulfillment-method-pickup-store/src/util/validateOrderMethodsstore.js b/packages/api-plugin-fulfillment-method-pickup-store/src/util/validateOrderMethodsStore.js similarity index 100% rename from packages/api-plugin-fulfillment-method-pickup-store/src/util/validateOrderMethodsstore.js rename to packages/api-plugin-fulfillment-method-pickup-store/src/util/validateOrderMethodsStore.js From 01175805567652f5ef26a56303e01355a0861689 Mon Sep 17 00:00:00 2001 From: Sujith Date: Mon, 13 Mar 2023 15:27:34 +0530 Subject: [PATCH 65/71] fix: pnpm-lock merge Signed-off-by: Sujith --- pnpm-lock.yaml | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4d45ff228c5..328552b06bc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -705,39 +705,35 @@ importers: '@reactioncommerce/api-plugin-carts': link:../api-plugin-carts '@reactioncommerce/data-factory': 1.0.1 - packages/api-plugin-fulfillment-method-shipping-dynamic-rate: + packages/api-plugin-fulfillment-method-pickup-store: specifiers: '@reactioncommerce/api-utils': ^1.16.9 + '@reactioncommerce/logger': ^1.1.4 '@reactioncommerce/reaction-error': ^1.0.1 simpl-schema: ^1.12.2 dependencies: '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/logger': link:../logger '@reactioncommerce/reaction-error': link:../reaction-error simpl-schema: 1.12.3 - packages/api-plugin-fulfillment-method-shipping-flat-rate: + packages/api-plugin-fulfillment-method-shipping-dynamic-rate: specifiers: '@reactioncommerce/api-utils': ^1.16.9 - '@reactioncommerce/logger': ^1.1.3 - '@reactioncommerce/random': ~1.0.2 '@reactioncommerce/reaction-error': ^1.0.1 - lodash: ^4.17.15 simpl-schema: ^1.12.2 dependencies: '@reactioncommerce/api-utils': link:../api-utils - '@reactioncommerce/logger': link:../logger - '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error - lodash: 4.17.21 simpl-schema: 1.12.3 - packages/api-plugin-fulfillment-type-shipping: + packages/api-plugin-fulfillment-method-shipping-flat-rate: specifiers: '@reactioncommerce/api-utils': ^1.16.9 '@reactioncommerce/logger': ^1.1.3 '@reactioncommerce/random': ~1.0.2 '@reactioncommerce/reaction-error': ^1.0.1 - lodash: ^4.17.21 + lodash: ^4.17.15 simpl-schema: ^1.12.2 dependencies: '@reactioncommerce/api-utils': link:../api-utils @@ -747,27 +743,31 @@ importers: lodash: 4.17.21 simpl-schema: 1.12.3 - packages/api-plugin-fulfillment-method-pickup-store: + packages/api-plugin-fulfillment-type-pickup: specifiers: '@reactioncommerce/api-utils': ^1.16.9 - '@reactioncommerce/logger': ^1.1.4 - '@reactioncommerce/reaction-error': ^1.0.1 - simpl-schema: ^1.12.2 + '@reactioncommerce/random': ~1.0.2 + '@reactioncommerce/reaction-error': 1.0.1 dependencies: '@reactioncommerce/api-utils': link:../api-utils - '@reactioncommerce/logger': link:../logger + '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error - simpl-schema: 1.12.3 - packages/api-plugin-fulfillment-type-pickup: + packages/api-plugin-fulfillment-type-shipping: specifiers: '@reactioncommerce/api-utils': ^1.16.9 + '@reactioncommerce/logger': ^1.1.3 '@reactioncommerce/random': ~1.0.2 - '@reactioncommerce/reaction-error': 1.0.1 + '@reactioncommerce/reaction-error': ^1.0.1 + lodash: ^4.17.21 + simpl-schema: ^1.12.2 dependencies: '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error + lodash: 4.17.21 + simpl-schema: 1.12.3 packages/api-plugin-i18n: specifiers: From 8cf2151e24075fd8abd0cbe9422433e48a2ac8b7 Mon Sep 17 00:00:00 2001 From: Sujith Date: Mon, 13 Mar 2023 16:17:59 +0530 Subject: [PATCH 66/71] fix: failing test fixes Signed-off-by: Sujith --- packages/api-plugin-orders/src/mutations/placeOrder.test.js | 5 +++++ .../src/util/orderValidators/prepareOrder.js | 2 +- .../util/orderValidators/validateInitialOrderData.test.js | 6 +++--- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/api-plugin-orders/src/mutations/placeOrder.test.js b/packages/api-plugin-orders/src/mutations/placeOrder.test.js index 2f9e5e625e8..703ba7afa7d 100644 --- a/packages/api-plugin-orders/src/mutations/placeOrder.test.js +++ b/packages/api-plugin-orders/src/mutations/placeOrder.test.js @@ -54,6 +54,11 @@ test("places an anonymous $0 order with no cartId and no payments", async () => availablePaymentMethods: ["PAYMENT1"] }]); + mockContext.queries.getDiscountsTotalForCart = jest.fn().mockName("getDiscountsTotalForCart").mockReturnValueOnce({ + discounts: [], + total: 0 + }); + const orderInput = Factory.orderInputSchema.makeOne({ billingAddress: null, cartId: null, diff --git a/packages/api-plugin-orders/src/util/orderValidators/prepareOrder.js b/packages/api-plugin-orders/src/util/orderValidators/prepareOrder.js index 3ec3a9a9b71..1379997a9f0 100644 --- a/packages/api-plugin-orders/src/util/orderValidators/prepareOrder.js +++ b/packages/api-plugin-orders/src/util/orderValidators/prepareOrder.js @@ -97,7 +97,7 @@ export default async function prepareOrder(context, input, mode) { validationResults.push(...validationErrors); return { errors: validationResults, success: false }; } - + const { shop, cart } = initialValidationResult; // Step03: Extract the rest of the required variables diff --git a/packages/api-plugin-orders/src/util/orderValidators/validateInitialOrderData.test.js b/packages/api-plugin-orders/src/util/orderValidators/validateInitialOrderData.test.js index 1308cd7a6a3..086620299ac 100644 --- a/packages/api-plugin-orders/src/util/orderValidators/validateInitialOrderData.test.js +++ b/packages/api-plugin-orders/src/util/orderValidators/validateInitialOrderData.test.js @@ -18,7 +18,7 @@ test("should throw if no shop retrieved using shopId provided", async () => { test("should throw if no cart retrieved using cartId provided", async () => { mockContext.queries.shopById = jest.fn().mockReturnValueOnce({ shopId: "shop123" }); - mockContext.collections.Cart.findOne = jest.fn().mockReturnValueOnce(undefined); + mockContext.queries.getCartById = jest.fn().mockReturnValueOnce(undefined); const cleanedInput = { order: { cartId: "cart123", shopId: "shop123" } }; const errorExpected = new ReactionError("not-found", "Cart not found while trying to validate order data", { field: "CartId", value: "cart123" }); await expect(validateInitialOrderData(mockContext, cleanedInput)).rejects.toThrow(errorExpected); @@ -26,7 +26,7 @@ test("should throw if no cart retrieved using cartId provided", async () => { test("should return shop and cart details", async () => { mockContext.queries.shopById = jest.fn().mockReturnValueOnce({ shopId: "shop123" }); - mockContext.collections.Cart.findOne = jest.fn().mockReturnValueOnce({ cartId: "cart123" }); + mockContext.queries.getCartById = jest.fn().mockReturnValueOnce({ cartId: "cart123" }); const cleanedInput = { order: { cartId: "cart123", shopId: "shop123" } }; const resultExpected = { cart: { cartId: "cart123" }, shop: { shopId: "shop123" } }; const result = await validateInitialOrderData(mockContext, cleanedInput); @@ -36,7 +36,7 @@ test("should return shop and cart details", async () => { test("should throw if no userId and No guest checkout", async () => { mockContext.userId = undefined; mockContext.queries.shopById = jest.fn().mockReturnValueOnce({ shopId: "shop123" }); - mockContext.collections.Cart.findOne = jest.fn().mockReturnValueOnce({ cartId: "cart123" }); + mockContext.queries.getCartById = jest.fn().mockReturnValueOnce({ cartId: "cart123" }); const cleanedInput = { order: { cartId: "cart123", shopId: "shop123" } }; const errorExpected = new ReactionError("access-denied", "Guest checkout not allowed"); await expect(validateInitialOrderData(mockContext, cleanedInput)).rejects.toThrow(errorExpected); From 17909c3e9c95d1cbb6b2b2b1025cb5b3447a514f Mon Sep 17 00:00:00 2001 From: Sujith Date: Mon, 13 Mar 2023 17:34:27 +0530 Subject: [PATCH 67/71] fix: missed out review comment Signed-off-by: Sujith --- .../util/orderValidators/getFinalFulfillmentGroups.js | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/packages/api-plugin-orders/src/util/orderValidators/getFinalFulfillmentGroups.js b/packages/api-plugin-orders/src/util/orderValidators/getFinalFulfillmentGroups.js index e1743451848..4b8afdeaf62 100644 --- a/packages/api-plugin-orders/src/util/orderValidators/getFinalFulfillmentGroups.js +++ b/packages/api-plugin-orders/src/util/orderValidators/getFinalFulfillmentGroups.js @@ -54,15 +54,8 @@ export default async function getFinalFulfillmentGroups(context, inputData) { // Build the final order item objects. As part of this, we look up the variant in the system and make sure that // the price is what the caller expects it to be. - if (items) { - group.items = await Promise.all(items.map((inputItem) => buildOrderItem(context, { currencyCode, inputItem, cart }))); - } else { - group.items = []; - } - - if (Array.isArray(additionalItems) && additionalItems.length) { - group.items.push(...additionalItems); - } + group.items = await Promise.all((items || []).map((inputItem) => buildOrderItem(context, { currencyCode, inputItem, cart }))); + group.items.push(...(additionalItems || [])); // Add some more properties for convenience group.itemIds = group.items.map((item) => item._id); From fe15a5528a27b8d6e21162699f59037516d905b5 Mon Sep 17 00:00:00 2001 From: Sujith Date: Thu, 18 May 2023 12:28:44 +0530 Subject: [PATCH 68/71] fix: fixed test errors Signed-off-by: Sujith --- .../src/schemas/schema.graphql | 4 +- .../getFlatRateFulfillmentRestriction.js | 2 +- .../getFlatRateFulfillmentRestrictions.js | 2 +- pnpm-lock.yaml | 38 +++++++++---------- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/packages/api-plugin-fulfillment-method-pickup-store/src/schemas/schema.graphql b/packages/api-plugin-fulfillment-method-pickup-store/src/schemas/schema.graphql index 7eea8eac63b..2b6b7c5e10e 100644 --- a/packages/api-plugin-fulfillment-method-pickup-store/src/schemas/schema.graphql +++ b/packages/api-plugin-fulfillment-method-pickup-store/src/schemas/schema.graphql @@ -6,9 +6,9 @@ type StoreFields { } "Additional Store data - Pickup" -type StoreData { +type storeData { gqlType: String storeData: [StoreFields] } -extend union AdditionalData = StoreData +extend union AdditionalData = storeData diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/getFlatRateFulfillmentRestriction.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/getFlatRateFulfillmentRestriction.js index 5e1787c95e2..8e5bb5cac9a 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/getFlatRateFulfillmentRestriction.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/getFlatRateFulfillmentRestriction.js @@ -1,4 +1,4 @@ -import flatRateFulfillmentRestriction from "./flatRateFulfillmentRestriction"; +import flatRateFulfillmentRestriction from "./flatRateFulfillmentRestriction.js"; /** * @name getFlatRateFulfillmentRestriction diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/getFlatRateFulfillmentRestrictions.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/getFlatRateFulfillmentRestrictions.js index 95a0fa294b2..6e38ca7534b 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/getFlatRateFulfillmentRestrictions.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/queries/getFlatRateFulfillmentRestrictions.js @@ -1,4 +1,4 @@ -import flatRateFulfillmentRestrictions from "./flatRateFulfillmentRestrictions"; +import flatRateFulfillmentRestrictions from "./flatRateFulfillmentRestrictions.js"; /** * @name getFlatRateFulfillmentRestrictions diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4d45ff228c5..53f536ae4d5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -705,39 +705,35 @@ importers: '@reactioncommerce/api-plugin-carts': link:../api-plugin-carts '@reactioncommerce/data-factory': 1.0.1 - packages/api-plugin-fulfillment-method-shipping-dynamic-rate: + packages/api-plugin-fulfillment-method-pickup-store: specifiers: '@reactioncommerce/api-utils': ^1.16.9 + '@reactioncommerce/logger': ^1.1.4 '@reactioncommerce/reaction-error': ^1.0.1 simpl-schema: ^1.12.2 dependencies: '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/logger': link:../logger '@reactioncommerce/reaction-error': link:../reaction-error simpl-schema: 1.12.3 - packages/api-plugin-fulfillment-method-shipping-flat-rate: + packages/api-plugin-fulfillment-method-shipping-dynamic-rate: specifiers: '@reactioncommerce/api-utils': ^1.16.9 - '@reactioncommerce/logger': ^1.1.3 - '@reactioncommerce/random': ~1.0.2 '@reactioncommerce/reaction-error': ^1.0.1 - lodash: ^4.17.15 simpl-schema: ^1.12.2 dependencies: '@reactioncommerce/api-utils': link:../api-utils - '@reactioncommerce/logger': link:../logger - '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error - lodash: 4.17.21 simpl-schema: 1.12.3 - packages/api-plugin-fulfillment-type-shipping: + packages/api-plugin-fulfillment-method-shipping-flat-rate: specifiers: '@reactioncommerce/api-utils': ^1.16.9 '@reactioncommerce/logger': ^1.1.3 '@reactioncommerce/random': ~1.0.2 '@reactioncommerce/reaction-error': ^1.0.1 - lodash: ^4.17.21 + lodash: ^4.17.15 simpl-schema: ^1.12.2 dependencies: '@reactioncommerce/api-utils': link:../api-utils @@ -747,27 +743,31 @@ importers: lodash: 4.17.21 simpl-schema: 1.12.3 - packages/api-plugin-fulfillment-method-pickup-store: + packages/api-plugin-fulfillment-type-pickup: specifiers: '@reactioncommerce/api-utils': ^1.16.9 - '@reactioncommerce/logger': ^1.1.4 - '@reactioncommerce/reaction-error': ^1.0.1 - simpl-schema: ^1.12.2 + '@reactioncommerce/random': ~1.0.2 + '@reactioncommerce/reaction-error': 1.0.1 dependencies: '@reactioncommerce/api-utils': link:../api-utils - '@reactioncommerce/logger': link:../logger + '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error - simpl-schema: 1.12.3 - packages/api-plugin-fulfillment-type-pickup: + packages/api-plugin-fulfillment-type-shipping: specifiers: '@reactioncommerce/api-utils': ^1.16.9 + '@reactioncommerce/logger': ^1.1.3 '@reactioncommerce/random': ~1.0.2 - '@reactioncommerce/reaction-error': 1.0.1 + '@reactioncommerce/reaction-error': ^1.0.1 + lodash: ^4.17.21 + simpl-schema: ^1.12.2 dependencies: '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error + lodash: 4.17.21 + simpl-schema: 1.12.3 packages/api-plugin-i18n: specifiers: @@ -8750,7 +8750,7 @@ packages: resolution: {integrity: sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==} engines: {node: '>= 4.0'} os: [darwin] - deprecated: fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2. + deprecated: The v1 package contains DANGEROUS / INSECURE binaries. Upgrade to safe fsevents v2 requiresBuild: true dependencies: bindings: 1.5.0 From 13bab48f2fbfcefe9dbbd6ba4e25dac8f9563d71 Mon Sep 17 00:00:00 2001 From: Sujith Date: Thu, 18 May 2023 18:00:31 +0530 Subject: [PATCH 69/71] fix: linter error Signed-off-by: Sujith --- packages/api-plugin-fulfillment/src/preStartup.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/api-plugin-fulfillment/src/preStartup.js b/packages/api-plugin-fulfillment/src/preStartup.js index d76a69f3415..6cd3b1dcac8 100644 --- a/packages/api-plugin-fulfillment/src/preStartup.js +++ b/packages/api-plugin-fulfillment/src/preStartup.js @@ -1,10 +1,7 @@ -import Logger from "@reactioncommerce/logger"; -import ReactionError from "@reactioncommerce/reaction-error"; import doesDatabaseVersionMatch from "@reactioncommerce/db-version-check"; import { migrationsNamespace } from "../migrations/migrationsNamespace.js"; import { extendFulfillmentSchemas } from "./simpleSchemas.js"; -const logCtx = { name: "fulfillment", file: "preStartup" }; const expectedVersion = 4; /** From df5ed5e2f8ef9509490c2e90af20f9b80f439773 Mon Sep 17 00:00:00 2001 From: Sujith Date: Thu, 18 May 2023 22:12:36 +0530 Subject: [PATCH 70/71] fix: changes picked from closed pr 6543 Signed-off-by: Sujith --- .../src/util/defaultRoles.js | 1 + packages/api-plugin-fulfillment/src/index.js | 16 ++++++++++++++++ .../src/schemas/schema.graphql | 14 ++++++++++++++ .../src/mutations/createProduct.js | 12 +++++++++++- 4 files changed, 42 insertions(+), 1 deletion(-) diff --git a/packages/api-plugin-authorization-simple/src/util/defaultRoles.js b/packages/api-plugin-authorization-simple/src/util/defaultRoles.js index 7e2cc6cc158..9e86126597d 100644 --- a/packages/api-plugin-authorization-simple/src/util/defaultRoles.js +++ b/packages/api-plugin-authorization-simple/src/util/defaultRoles.js @@ -60,6 +60,7 @@ export const defaultShopManagerRoles = [ "reaction:legacy:products/update:prices", "reaction:legacy:products/update", "reaction:legacy:shipping-rates/update:settings", + "reaction:legacy:fulfillmentTypes/update:settings", "reaction:legacy:fulfillmentTypes/create", "reaction:legacy:fulfillmentTypes/delete", "reaction:legacy:fulfillmentTypes/read", diff --git a/packages/api-plugin-fulfillment/src/index.js b/packages/api-plugin-fulfillment/src/index.js index 079be3a337b..64f22e78cc9 100644 --- a/packages/api-plugin-fulfillment/src/index.js +++ b/packages/api-plugin-fulfillment/src/index.js @@ -52,6 +52,22 @@ export default async function register(app) { }, simpleSchemas: { MethodEmptyData + }, + shopSettingsConfig: { + baseFulfillmentTypesForShop: { + permissionsThatCanEdit: ["reaction:legacy:fulfillmentTypes/update:settings"], + simpleSchema: { + type: { + "baseFulfillmentTypesForShop": { + type: Array, + optional: true + }, + "baseFulfillmentTypesForShop.$": { + type: String + } + } + } + } } }); } diff --git a/packages/api-plugin-fulfillment/src/schemas/schema.graphql b/packages/api-plugin-fulfillment/src/schemas/schema.graphql index b55aec82ce7..aee0988926b 100644 --- a/packages/api-plugin-fulfillment/src/schemas/schema.graphql +++ b/packages/api-plugin-fulfillment/src/schemas/schema.graphql @@ -1,3 +1,17 @@ +extend type ShopSettings { + """ + List of fulfillment types that will be added to each new product by default + """ + baseFulfillmentTypesForShop: [FulfillmentType] +} + +extend input ShopSettingsUpdates { + """ + The input for default fulfillment type for shop should be one of the installed fulfillment type plugin + """ + baseFulfillmentTypesForShop: [FulfillmentType] +} + "Allowed fulfillment types, extended by fulfillment-type plugins" enum FulfillmentType { "Default fulfillment type when none is decided by user" diff --git a/packages/api-plugin-products/src/mutations/createProduct.js b/packages/api-plugin-products/src/mutations/createProduct.js index 2d6068bc4d6..70bab07da2b 100644 --- a/packages/api-plugin-products/src/mutations/createProduct.js +++ b/packages/api-plugin-products/src/mutations/createProduct.js @@ -49,6 +49,15 @@ export default async function createProduct(context, input) { throw new ReactionError("invalid-param", "Creating a deleted product is not allowed"); } + // Include the base/shop-level fulfillment types for all products + // if user has provided additional fulfillment types, include them also + const { baseFulfillmentTypesForShop } = await context.queries.appSettings(context, shopId); + const { supportedFulfillmentTypes } = initialProductData; + let combinedSupportedFulfillmentTypes = []; + if (baseFulfillmentTypesForShop) combinedSupportedFulfillmentTypes = [...baseFulfillmentTypesForShop]; + if (supportedFulfillmentTypes) combinedSupportedFulfillmentTypes = [...combinedSupportedFulfillmentTypes, ...supportedFulfillmentTypes]; + combinedSupportedFulfillmentTypes = [...new Set(combinedSupportedFulfillmentTypes)]; + const createdAt = new Date(); const newProduct = { _id: newProductId, @@ -59,7 +68,6 @@ export default async function createProduct(context, input) { isVisible: false, shopId, shouldAppearInSitemap: true, - supportedFulfillmentTypes: initialProductData.supportedFulfillmentTypes || [], title: "", type: "simple", updatedAt: createdAt, @@ -68,6 +76,8 @@ export default async function createProduct(context, input) { }, ...initialProductData }; + // Adding this outside to ensure that this is not over-written by the original initialProductData.supportedFulfillmentTypes + newProduct.supportedFulfillmentTypes = combinedSupportedFulfillmentTypes || []; // Apply custom transformations from plugins. for (const customFunc of context.getFunctionsOfType("mutateNewProductBeforeCreate")) { From 7503a643cc7a496faf1c31aca8922611057e20e2 Mon Sep 17 00:00:00 2001 From: Sujith Date: Thu, 18 May 2023 23:17:08 +0530 Subject: [PATCH 71/71] fix: picked chgs from closed pr 6545 Signed-off-by: Sujith --- .../src/i18n/en.json | 23 +++++ .../src/i18n/index.js | 12 +++ .../src/index.js | 2 + .../src/i18n/en.json | 23 +++++ .../src/i18n/index.js | 12 +++ .../src/index.js | 2 + .../src/i18n/ar.json | 23 +++++ .../src/i18n/bg.json | 23 +++++ .../src/i18n/cs.json | 23 +++++ .../src/i18n/de.json | 23 +++++ .../src/i18n/el.json | 23 +++++ .../src/i18n/en.json | 24 +++++ .../src/i18n/es.json | 23 +++++ .../src/i18n/fr.json | 23 +++++ .../src/i18n/he.json | 5 ++ .../src/i18n/hr.json | 23 +++++ .../src/i18n/hu.json | 23 +++++ .../src/i18n/index.js | 58 +++++++++++++ .../src/i18n/it.json | 23 +++++ .../src/i18n/my.json | 23 +++++ .../src/i18n/nb.json | 5 ++ .../src/i18n/nl.json | 23 +++++ .../src/i18n/pl.json | 23 +++++ .../src/i18n/pt.json | 23 +++++ .../src/i18n/ro.json | 23 +++++ .../src/i18n/ru.json | 23 +++++ .../src/i18n/sl.json | 23 +++++ .../src/i18n/sv.json | 23 +++++ .../src/i18n/tr.json | 23 +++++ .../src/i18n/vi.json | 23 +++++ .../src/i18n/zh.json | 23 +++++ .../src/index.js | 2 + .../src/i18n/en.json | 58 +++++++++++++ .../src/i18n/index.js | 12 +++ .../src/index.js | 2 + .../src/i18n/ar.json | 81 +++++++++++++++++ .../src/i18n/bg.json | 81 +++++++++++++++++ .../src/i18n/cs.json | 81 +++++++++++++++++ .../src/i18n/de.json | 81 +++++++++++++++++ .../src/i18n/el.json | 81 +++++++++++++++++ .../src/i18n/en.json | 84 ++++++++++++++++++ .../src/i18n/es.json | 87 +++++++++++++++++++ .../src/i18n/fr.json | 81 +++++++++++++++++ .../src/i18n/he.json | 5 ++ .../src/i18n/hr.json | 81 +++++++++++++++++ .../src/i18n/hu.json | 81 +++++++++++++++++ .../src/i18n/index.js | 54 ++++++++++++ .../src/i18n/it.json | 81 +++++++++++++++++ .../src/i18n/my.json | 81 +++++++++++++++++ .../src/i18n/nb.json | 5 ++ .../src/i18n/nl.json | 81 +++++++++++++++++ .../src/i18n/pl.json | 81 +++++++++++++++++ .../src/i18n/pt.json | 81 +++++++++++++++++ .../src/i18n/ro.json | 81 +++++++++++++++++ .../src/i18n/ru.json | 81 +++++++++++++++++ .../src/i18n/sl.json | 81 +++++++++++++++++ .../src/i18n/sv.json | 81 +++++++++++++++++ .../src/i18n/tr.json | 81 +++++++++++++++++ .../src/i18n/vi.json | 81 +++++++++++++++++ .../src/i18n/zh.json | 81 +++++++++++++++++ .../src/index.js | 2 + .../api-plugin-fulfillment/src/i18n/en.json | 37 ++++++++ .../api-plugin-fulfillment/src/i18n/index.js | 12 +++ packages/api-plugin-fulfillment/src/index.js | 2 + 64 files changed, 2631 insertions(+) create mode 100644 packages/api-plugin-fulfillment-method-pickup-store/src/i18n/en.json create mode 100644 packages/api-plugin-fulfillment-method-pickup-store/src/i18n/index.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/i18n/en.json create mode 100644 packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/i18n/index.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/ar.json create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/bg.json create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/cs.json create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/de.json create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/el.json create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/en.json create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/es.json create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/fr.json create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/he.json create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/hr.json create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/hu.json create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/index.js create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/it.json create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/my.json create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/nb.json create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/nl.json create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/pl.json create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/pt.json create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/ro.json create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/ru.json create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/sl.json create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/sv.json create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/tr.json create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/vi.json create mode 100644 packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/zh.json create mode 100644 packages/api-plugin-fulfillment-type-pickup/src/i18n/en.json create mode 100644 packages/api-plugin-fulfillment-type-pickup/src/i18n/index.js create mode 100644 packages/api-plugin-fulfillment-type-shipping/src/i18n/ar.json create mode 100644 packages/api-plugin-fulfillment-type-shipping/src/i18n/bg.json create mode 100644 packages/api-plugin-fulfillment-type-shipping/src/i18n/cs.json create mode 100644 packages/api-plugin-fulfillment-type-shipping/src/i18n/de.json create mode 100644 packages/api-plugin-fulfillment-type-shipping/src/i18n/el.json create mode 100644 packages/api-plugin-fulfillment-type-shipping/src/i18n/en.json create mode 100644 packages/api-plugin-fulfillment-type-shipping/src/i18n/es.json create mode 100644 packages/api-plugin-fulfillment-type-shipping/src/i18n/fr.json create mode 100644 packages/api-plugin-fulfillment-type-shipping/src/i18n/he.json create mode 100644 packages/api-plugin-fulfillment-type-shipping/src/i18n/hr.json create mode 100644 packages/api-plugin-fulfillment-type-shipping/src/i18n/hu.json create mode 100644 packages/api-plugin-fulfillment-type-shipping/src/i18n/index.js create mode 100644 packages/api-plugin-fulfillment-type-shipping/src/i18n/it.json create mode 100644 packages/api-plugin-fulfillment-type-shipping/src/i18n/my.json create mode 100644 packages/api-plugin-fulfillment-type-shipping/src/i18n/nb.json create mode 100644 packages/api-plugin-fulfillment-type-shipping/src/i18n/nl.json create mode 100644 packages/api-plugin-fulfillment-type-shipping/src/i18n/pl.json create mode 100644 packages/api-plugin-fulfillment-type-shipping/src/i18n/pt.json create mode 100644 packages/api-plugin-fulfillment-type-shipping/src/i18n/ro.json create mode 100644 packages/api-plugin-fulfillment-type-shipping/src/i18n/ru.json create mode 100644 packages/api-plugin-fulfillment-type-shipping/src/i18n/sl.json create mode 100644 packages/api-plugin-fulfillment-type-shipping/src/i18n/sv.json create mode 100644 packages/api-plugin-fulfillment-type-shipping/src/i18n/tr.json create mode 100644 packages/api-plugin-fulfillment-type-shipping/src/i18n/vi.json create mode 100644 packages/api-plugin-fulfillment-type-shipping/src/i18n/zh.json create mode 100644 packages/api-plugin-fulfillment/src/i18n/en.json create mode 100644 packages/api-plugin-fulfillment/src/i18n/index.js diff --git a/packages/api-plugin-fulfillment-method-pickup-store/src/i18n/en.json b/packages/api-plugin-fulfillment-method-pickup-store/src/i18n/en.json new file mode 100644 index 00000000000..5ecdb9cd6c6 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-pickup-store/src/i18n/en.json @@ -0,0 +1,23 @@ +[{ + "i18n": "en", + "ns": "reaction-pickup-store", + "translation": { + "reaction-pickup-store": { + "admin": { + "pickupSettings": { + "header": "Pickup", + "methodLabel": "Store", + "rateSaved": "Fulfillment method saved", + "rateFailed": "Fulfillment method update failed", + "noRatesFound": "No Store pickup fulfillment methods found" + }, + "pickupGrid": { + "name": "Name", + "group": "Group", + "rate": "Rate", + "label": "Label" + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-method-pickup-store/src/i18n/index.js b/packages/api-plugin-fulfillment-method-pickup-store/src/i18n/index.js new file mode 100644 index 00000000000..34f686a3c25 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-pickup-store/src/i18n/index.js @@ -0,0 +1,12 @@ +import en from "./en.json"; + +// +// we want all the files in individual +// imports for easier handling by +// automated translation software +// +export default { + translations: [ + ...en + ] +}; diff --git a/packages/api-plugin-fulfillment-method-pickup-store/src/index.js b/packages/api-plugin-fulfillment-method-pickup-store/src/index.js index 05bb2603785..f3f4fa2ac3f 100644 --- a/packages/api-plugin-fulfillment-method-pickup-store/src/index.js +++ b/packages/api-plugin-fulfillment-method-pickup-store/src/index.js @@ -1,4 +1,5 @@ import { createRequire } from "module"; +import i18n from "./i18n/index.js"; import fulfillmentMethodPickupStorePreStartup from "./preStartup.js"; import fulfillmentMethodPickupStoreStartup from "./startup.js"; import { MethodStoreData } from "./simpleSchemas.js"; @@ -19,6 +20,7 @@ export default async function register(app) { label: "Fulfillment Method Pickup Store", name: "fulfillment-method-pickup-store", version: pkg.version, + i18n, graphQL: { schemas }, diff --git a/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/i18n/en.json b/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/i18n/en.json new file mode 100644 index 00000000000..5e3bd2fdaf9 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/i18n/en.json @@ -0,0 +1,23 @@ +[{ + "i18n": "en", + "ns": "reaction-shipping-dynamic-rate", + "translation": { + "reaction-shipping-dynamic-rate": { + "admin": { + "shippingSettings": { + "header": "Shipping", + "methodLabel": "Dynamic Rate", + "rateSaved": "Fulfillment method saved", + "rateFailed": "Fulfillment method update failed", + "noRatesFound": "No dynamic rate fulfillment methods found" + }, + "shippingGrid": { + "name": "Name", + "group": "Group", + "rate": "Rate", + "label": "Label" + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/i18n/index.js b/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/i18n/index.js new file mode 100644 index 00000000000..34f686a3c25 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/i18n/index.js @@ -0,0 +1,12 @@ +import en from "./en.json"; + +// +// we want all the files in individual +// imports for easier handling by +// automated translation software +// +export default { + translations: [ + ...en + ] +}; diff --git a/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/index.js b/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/index.js index fefa6dddd74..36d1e9b6ad7 100644 --- a/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/index.js +++ b/packages/api-plugin-fulfillment-method-shipping-dynamic-rate/src/index.js @@ -1,5 +1,6 @@ import { createRequire } from "module"; import { MethodDynamicRateData } from "./simpleSchemas.js"; +import i18n from "./i18n/index.js"; import preStartup from "./preStartup.js"; import startup from "./startup.js"; import schemas from "./schemas/index.js"; @@ -19,6 +20,7 @@ export default async function register(app) { label: "Fulfillment Method Shipping Dynamic Rate", name: "fulfillment-method-shipping-dynamic-rate", version: pkg.version, + i18n, graphQL: { schemas }, diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/ar.json b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/ar.json new file mode 100644 index 00000000000..aa8c57e824c --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/ar.json @@ -0,0 +1,23 @@ +[{ + "i18n": "ar", + "ns": "reaction-shipping-rates", + "translation": { + "reaction-shipping-rates": { + "admin": { + "shippingSettings": { + "flatRateLabel": "معدل", + "confirmRateDelete": "حذف معدل؟", + "rateSaved": "معدل ثابت حفظها.", + "rateFailed": "فشل تحديث معدل ثابت.", + "noRatesFound": "لم يتم العثور على أسعار الفائدة." + }, + "shippingGrid": { + "name": "اسم", + "group": "مجموعة", + "rate": "معدل", + "label": "ملصق" + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/bg.json b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/bg.json new file mode 100644 index 00000000000..047b85042cc --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/bg.json @@ -0,0 +1,23 @@ +[{ + "i18n": "bg", + "ns": "reaction-shipping-rates", + "translation": { + "reaction-shipping-rates": { + "admin": { + "shippingSettings": { + "flatRateLabel": "Единна ставка", + "confirmRateDelete": "ставка Изтриване?", + "rateSaved": "Единна ставка спасен.", + "rateFailed": "актуализация Единна ставка провали.", + "noRatesFound": "Няма намерени проценти." + }, + "shippingGrid": { + "name": "Име", + "group": "група", + "rate": "скорост", + "label": "Етикет" + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/cs.json b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/cs.json new file mode 100644 index 00000000000..ce87a35442b --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/cs.json @@ -0,0 +1,23 @@ +[{ + "i18n": "cs", + "ns": "reaction-shipping-rates", + "translation": { + "reaction-shipping-rates": { + "admin": { + "shippingSettings": { + "flatRateLabel": "jednotná sazba", + "confirmRateDelete": "Smazat míru?", + "rateSaved": "Paušální částka uložena.", + "rateFailed": "Byt rychlost aktualizace nezdařila.", + "noRatesFound": "Nebyly nalezeny žádné ceny." + }, + "shippingGrid": { + "name": "Název", + "group": "Skupina", + "rate": "Rychlost", + "label": "Označení" + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/de.json b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/de.json new file mode 100644 index 00000000000..dd9656a6969 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/de.json @@ -0,0 +1,23 @@ +[{ + "i18n": "de", + "ns": "reaction-shipping-rates", + "translation": { + "reaction-shipping-rates": { + "admin": { + "shippingSettings": { + "flatRateLabel": "Flatrate", + "confirmRateDelete": "Löschen Rate?", + "rateSaved": "Flatrate gespeichert.", + "rateFailed": "Flatrate-Update ist fehlgeschlagen.", + "noRatesFound": "Keine Preise gefunden." + }, + "shippingGrid": { + "name": "Name", + "group": "Gruppe", + "rate": "Preis", + "label": "Etikette" + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/el.json b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/el.json new file mode 100644 index 00000000000..605891ba538 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/el.json @@ -0,0 +1,23 @@ +[{ + "i18n": "el", + "ns": "reaction-shipping-rates", + "translation": { + "reaction-shipping-rates": { + "admin": { + "shippingSettings": { + "flatRateLabel": "Flat Rate", + "confirmRateDelete": "Διαγραφή ποσοστό;", + "rateSaved": "Κατ 'αποκοπή ποσό σωθεί.", + "rateFailed": "ενημέρωση κατ 'αποκοπή απέτυχε.", + "noRatesFound": "Δεν βρέθηκαν τιμές." + }, + "shippingGrid": { + "name": "Όνομα", + "group": "Ομάδα", + "rate": "Τιμή", + "label": "Επιγραφή" + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/en.json b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/en.json new file mode 100644 index 00000000000..ba61f2fe99d --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/en.json @@ -0,0 +1,24 @@ +[{ + "i18n": "en", + "ns": "reaction-shipping-rates", + "translation": { + "reaction-shipping-rates": { + "admin": { + "shippingSettings": { + "header": "Shipping", + "flatRateLabel": "Flat Rate", + "confirmRateDelete": "Delete this fulfillment method?", + "rateSaved": "Fulfillment method saved", + "rateFailed": "Fulfillment method update failed", + "noRatesFound": "No flat rate fulfillment methods found" + }, + "shippingGrid": { + "name": "Name", + "group": "Group", + "rate": "Rate", + "label": "Label" + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/es.json b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/es.json new file mode 100644 index 00000000000..bfee545f307 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/es.json @@ -0,0 +1,23 @@ +[{ + "i18n": "es", + "ns": "reaction-shipping-rates", + "translation": { + "reaction-shipping-rates": { + "admin": { + "shippingSettings": { + "flatRateLabel": "Tarifa plana", + "confirmRateDelete": "Eliminar tasa?", + "rateSaved": "La tarifa plana de guarda.", + "rateFailed": "actualización de tarifa plana falló.", + "noRatesFound": "No se encontraron tasas." + }, + "shippingGrid": { + "name": "Nombre", + "group": "Grupo", + "rate": "Tarifa", + "label": "Etiqueta" + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/fr.json b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/fr.json new file mode 100644 index 00000000000..bfbdf658258 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/fr.json @@ -0,0 +1,23 @@ +[{ + "i18n": "fr", + "ns": "reaction-shipping-rates", + "translation": { + "reaction-shipping-rates": { + "admin": { + "shippingSettings": { + "flatRateLabel": "Forfait", + "confirmRateDelete": "Supprimer le tarif ?", + "rateSaved": "Tarif forfaitaire sauvegardé.", + "rateFailed": "La mise à jour du tarif forfaitaire a échouée.", + "noRatesFound": "Pas de prix trouvés." + }, + "shippingGrid": { + "name": "Nom", + "group": "Groupe", + "rate": "Tarif", + "label": "Étiquette" + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/he.json b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/he.json new file mode 100644 index 00000000000..7d303dddd47 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/he.json @@ -0,0 +1,5 @@ +[{ + "i18n": "he", + "ns": "reaction-shipping-rates", + "translation": { } +}] diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/hr.json b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/hr.json new file mode 100644 index 00000000000..54f479d21e1 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/hr.json @@ -0,0 +1,23 @@ +[{ + "i18n": "hr", + "ns": "reaction-shipping-rates", + "translation": { + "reaction-shipping-rates": { + "admin": { + "shippingSettings": { + "flatRateLabel": "flat rate", + "confirmRateDelete": "Brisanje stopa?", + "rateSaved": "Stan stopa spasio.", + "rateFailed": "Flat rate ažuriranje nije uspjelo.", + "noRatesFound": "Nisu pronađene stope." + }, + "shippingGrid": { + "name": "Ime", + "group": "Skupina", + "rate": "Stopa", + "label": "Označiti" + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/hu.json b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/hu.json new file mode 100644 index 00000000000..a0f542840f3 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/hu.json @@ -0,0 +1,23 @@ +[{ + "i18n": "hu", + "ns": "reaction-shipping-rates", + "translation": { + "reaction-shipping-rates": { + "admin": { + "shippingSettings": { + "flatRateLabel": "Átalánydíj", + "confirmRateDelete": "Törlés arány?", + "rateSaved": "Átalánydíjas mentve.", + "rateFailed": "Átalánydíjas sikertelen volt.", + "noRatesFound": "Nem talált árak." + }, + "shippingGrid": { + "name": "Név", + "group": "Csoport", + "rate": "Arány", + "label": "Címke" + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/index.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/index.js new file mode 100644 index 00000000000..fe4f9543713 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/index.js @@ -0,0 +1,58 @@ +import ar from "./ar.json"; +import bg from "./bg.json"; +import cs from "./cs.json"; +import de from "./de.json"; +import el from "./el.json"; +import en from "./en.json"; +import es from "./es.json"; +import fr from "./fr.json"; +import he from "./he.json"; +import hr from "./hr.json"; +import hu from "./hu.json"; +import it from "./it.json"; +import my from "./my.json"; +import nb from "./nb.json"; +import nl from "./nl.json"; +import pl from "./pl.json"; +import pt from "./pt.json"; +import ro from "./ro.json"; +import ru from "./ru.json"; +import sl from "./sl.json"; +import sv from "./sv.json"; +import tr from "./tr.json"; +import vi from "./vi.json"; +import zh from "./zh.json"; + +// +// we want all the files in individual +// imports for easier handling by +// automated translation software +// +export default { + translations: [ + ...ar, + ...bg, + ...cs, + ...de, + ...el, + ...en, + ...es, + ...fr, + ...he, + ...hr, + ...hu, + ...it, + ...my, + ...nb, + ...nl, + ...pl, + ...pt, + ...ro, + ...ru, + ...sl, + ...sv, + ...tr, + ...vi, + ...zh + ] +}; diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/it.json b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/it.json new file mode 100644 index 00000000000..560a353d895 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/it.json @@ -0,0 +1,23 @@ +[{ + "i18n": "it", + "ns": "reaction-shipping-rates", + "translation": { + "reaction-shipping-rates": { + "admin": { + "shippingSettings": { + "flatRateLabel": "Tasso fisso", + "confirmRateDelete": "Eliminare tasso?", + "rateSaved": "Tariffa unica salvato.", + "rateFailed": "aggiornamento tasso di piatto non è riuscito.", + "noRatesFound": "Prezzi non trovato." + }, + "shippingGrid": { + "name": "Nome", + "group": "Gruppo", + "rate": "Tasso", + "label": "Etichetta" + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/my.json b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/my.json new file mode 100644 index 00000000000..252737fdda6 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/my.json @@ -0,0 +1,23 @@ +[{ + "i18n": "my", + "ns": "reaction-shipping-rates", + "translation": { + "reaction-shipping-rates": { + "admin": { + "shippingSettings": { + "flatRateLabel": "စျေးနှုန်းတစ်သတ်မှတ်တည်းဖြစ်သော", + "confirmRateDelete": "မှုနှုန်းကိုဖျက်ရမလား?", + "rateSaved": "ပြားချပ်ချပ်နှုန်းသည်ကယ်တင်ခြင်းသို့ရောက်ရ၏။", + "rateFailed": "ပြားချပ်ချပ်နှုန်းကို update ကိုပျက်ကွက်ခဲ့သည်။", + "noRatesFound": "အဘယ်သူမျှမနှုန်းထားများတွေ့ရှိခဲ့ပါတယ်။" + }, + "shippingGrid": { + "name": "နာမကိုအမှီ", + "group": "Group က", + "rate": "rate", + "label": "တံဆိပ်" + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/nb.json b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/nb.json new file mode 100644 index 00000000000..c84fa179366 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/nb.json @@ -0,0 +1,5 @@ +[{ + "i18n": "nb", + "ns": "reaction-shipping-rates", + "translation": { } +}] diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/nl.json b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/nl.json new file mode 100644 index 00000000000..754b04d23f5 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/nl.json @@ -0,0 +1,23 @@ +[{ + "i18n": "nl", + "ns": "reaction-shipping-rates", + "translation": { + "reaction-shipping-rates": { + "admin": { + "shippingSettings": { + "flatRateLabel": "Vast bedrag", + "confirmRateDelete": "rate verwijderen?", + "rateSaved": "Flat rate opgeslagen.", + "rateFailed": "Flat rate update is mislukt.", + "noRatesFound": "Geen prijzen gevonden." + }, + "shippingGrid": { + "name": "Naam", + "group": "Groep", + "rate": "Snelheid", + "label": "Etiket" + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/pl.json b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/pl.json new file mode 100644 index 00000000000..94cc73a56ba --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/pl.json @@ -0,0 +1,23 @@ +[{ + "i18n": "pl", + "ns": "reaction-shipping-rates", + "translation": { + "reaction-shipping-rates": { + "admin": { + "shippingSettings": { + "flatRateLabel": "Jednolita stawka", + "confirmRateDelete": "Usuń stopę?", + "rateSaved": "Ryczałt zapisane.", + "rateFailed": "Aktualizacja ryczałtowa nie powiodło się.", + "noRatesFound": "Nie znaleziono stopy." + }, + "shippingGrid": { + "name": "Nazwa", + "group": "Grupa", + "rate": "Oceniać", + "label": "Etykieta" + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/pt.json b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/pt.json new file mode 100644 index 00000000000..ff072aa8e94 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/pt.json @@ -0,0 +1,23 @@ +[{ + "i18n": "pt", + "ns": "reaction-shipping-rates", + "translation": { + "reaction-shipping-rates": { + "admin": { + "shippingSettings": { + "flatRateLabel": "Taxa fixa", + "confirmRateDelete": "Excluir taxa?", + "rateSaved": "Taxa fixa salvo.", + "rateFailed": "atualização de taxa fixa falhou.", + "noRatesFound": "Sem preço encontrado." + }, + "shippingGrid": { + "name": "Nome", + "group": "Grupo", + "rate": "Taxa", + "label": "Etiqueta" + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/ro.json b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/ro.json new file mode 100644 index 00000000000..416013c15a0 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/ro.json @@ -0,0 +1,23 @@ +[{ + "i18n": "ro", + "ns": "reaction-shipping-rates", + "translation": { + "reaction-shipping-rates": { + "admin": { + "shippingSettings": { + "flatRateLabel": "Preţ global", + "confirmRateDelete": "Rata șterge?", + "rateSaved": "Rata de plat salvat.", + "rateFailed": "actualizare rată fixă ​​a eșuat.", + "noRatesFound": "Nu există tarife găsite." + }, + "shippingGrid": { + "name": "Nume", + "group": "grup", + "rate": "Rată", + "label": "Eticheta" + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/ru.json b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/ru.json new file mode 100644 index 00000000000..26ca9402d71 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/ru.json @@ -0,0 +1,23 @@ +[{ + "i18n": "ru", + "ns": "reaction-shipping-rates", + "translation": { + "reaction-shipping-rates": { + "admin": { + "shippingSettings": { + "flatRateLabel": "Квартирная плата", + "confirmRateDelete": "Удалить ставку?", + "rateSaved": "Фиксированная ставка сохранена.", + "rateFailed": "обновление фиксированная ставка не удалось.", + "noRatesFound": "Нет ставок не найдено." + }, + "shippingGrid": { + "name": "Имя", + "group": "Группа", + "rate": "Ставка", + "label": "Этикетка" + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/sl.json b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/sl.json new file mode 100644 index 00000000000..ca2882bc497 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/sl.json @@ -0,0 +1,23 @@ +[{ + "i18n": "sl", + "ns": "reaction-shipping-rates", + "translation": { + "reaction-shipping-rates": { + "admin": { + "shippingSettings": { + "flatRateLabel": "Flat Rate", + "confirmRateDelete": "Brisanje hitrost?", + "rateSaved": "Pavšalno shranjene.", + "rateFailed": "Stanovanje posodobitev stopnja ni uspela.", + "noRatesFound": "Ni mere." + }, + "shippingGrid": { + "name": "Name", + "group": "skupina", + "rate": "Oceniti", + "label": "Label" + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/sv.json b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/sv.json new file mode 100644 index 00000000000..625a2ec1028 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/sv.json @@ -0,0 +1,23 @@ +[{ + "i18n": "sv", + "ns": "reaction-shipping-rates", + "translation": { + "reaction-shipping-rates": { + "admin": { + "shippingSettings": { + "flatRateLabel": "ENHETSTAXA", + "confirmRateDelete": "Radera frekvens?", + "rateSaved": "Schablonbelopp sparas.", + "rateFailed": "Platt uppdateringsfrekvens misslyckades.", + "noRatesFound": "Det finns inga priser hittades." + }, + "shippingGrid": { + "name": "Namn", + "group": "Grupp", + "rate": "Betygsätt", + "label": "Etikett" + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/tr.json b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/tr.json new file mode 100644 index 00000000000..57a7f4f662d --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/tr.json @@ -0,0 +1,23 @@ +[{ + "i18n": "tr", + "ns": "reaction-shipping-rates", + "translation": { + "reaction-shipping-rates": { + "admin": { + "shippingSettings": { + "flatRateLabel": "Sabit fiyat", + "confirmRateDelete": "oranı silinsin mi?", + "rateSaved": "Sabit ücret kaydedildi.", + "rateFailed": "Sabit ücret güncellemesi başarısız oldu.", + "noRatesFound": "Hiçbir oranlar bulunamadı." + }, + "shippingGrid": { + "name": "Isim", + "group": "grup", + "rate": "Oran", + "label": "Etiket" + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/vi.json b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/vi.json new file mode 100644 index 00000000000..0a4d4e1b902 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/vi.json @@ -0,0 +1,23 @@ +[{ + "i18n": "vi", + "ns": "reaction-shipping-rates", + "translation": { + "reaction-shipping-rates": { + "admin": { + "shippingSettings": { + "flatRateLabel": "giá thấp nhứt", + "confirmRateDelete": "Xóa tỷ lệ?", + "rateSaved": "tỷ lệ căn hộ lưu.", + "rateFailed": "cập nhật tỷ lệ căn hộ thất bại.", + "noRatesFound": "Không tìm thấy giá." + }, + "shippingGrid": { + "name": "Tên", + "group": "Nhóm", + "rate": "Tỷ lệ", + "label": "Nhãn" + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/zh.json b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/zh.json new file mode 100644 index 00000000000..333d5de6538 --- /dev/null +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/i18n/zh.json @@ -0,0 +1,23 @@ +[{ + "i18n": "zh", + "ns": "reaction-shipping-rates", + "translation": { + "reaction-shipping-rates": { + "admin": { + "shippingSettings": { + "flatRateLabel": "扁平率", + "confirmRateDelete": "删除率是多少?", + "rateSaved": "扁平率保存。", + "rateFailed": "统一费率更新失败。", + "noRatesFound": "没有发现率。" + }, + "shippingGrid": { + "name": "名字", + "group": "组", + "rate": "率", + "label": "标签" + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/index.js b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/index.js index f30ab088de9..f0547d4b097 100644 --- a/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/index.js +++ b/packages/api-plugin-fulfillment-method-shipping-flat-rate/src/index.js @@ -1,6 +1,7 @@ import { createRequire } from "module"; import fulfillmentMethodsWithQuotesShippingFlatRate from "./fulfillmentMethodsWithQuotesShippingFlatRate.js"; import validateOrderMethodsFlatRate from "./util/validateOrderMethodsFlatRate.js"; +import i18n from "./i18n/index.js"; import resolvers from "./resolvers/index.js"; import mutations from "./mutations/index.js"; import policies from "./policies.json"; @@ -29,6 +30,7 @@ export default async function register(app) { label: "Fulfillment Method Shipping Flat Rate", name: "fulfillment-method-shipping-flat-rate", version: pkg.version, + i18n, graphQL: { resolvers, schemas diff --git a/packages/api-plugin-fulfillment-type-pickup/src/i18n/en.json b/packages/api-plugin-fulfillment-type-pickup/src/i18n/en.json new file mode 100644 index 00000000000..947565b4fe3 --- /dev/null +++ b/packages/api-plugin-fulfillment-type-pickup/src/i18n/en.json @@ -0,0 +1,58 @@ +[{ + "i18n": "en", + "ns": "reaction-shipping", + "translation": { + "reaction-shipping": { + "admin": { + "dashboard": { + "pickupLabel": "Pickup", + "pickupTitle": "Pickup", + "pickupDescription": "Manage Pickup" + }, + "settings": { + "pickupLabel": "Pickup" + } + }, + "checkoutPickup": { + "selectPickupOption": "Select pickup option", + "noPickupMethods": "No pickup methods are configured.", + "contactAdmin": "Please contact the store administrator.", + "configureNow": "Configure now.", + "pickup": "Pickup" + }, + "pickup": { + "editPickupProvider": "Edit pickup provider", + "editPickupMethod": "Edit pickup method", + "noSettingsForThisView": "No settings for this view", + "noPickupMethods": "No pickup methods are configured.", + "pickupProviderSaved": "Pickup provider saved.", + "pickupProviderUpdated": "Pickup provider data updated.", + "pickupMethodRateAdded": "Pickup method rate added.", + "pickupMethodRateUpdated": "Pickup method rate updated.", + "name": "Name", + "label": "Label", + "group": "Group", + "cost": "Cost", + "handling": "Handling", + "rate": "Rate", + "enabled": "Enabled", + "disabled": "Disabled", + "updateRate": "Update {{name}} rate", + "provider": { + "name": "Service Code", + "label": "Public Label", + "enabled": "Enabled" + } + }, + "pickupMethod": { + "name": "Method Name", + "label": "Public Label", + "group": "Group", + "cost": "Cost", + "handling": "Handling", + "rate": "Rate", + "enabled": "Enabled" + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-type-pickup/src/i18n/index.js b/packages/api-plugin-fulfillment-type-pickup/src/i18n/index.js new file mode 100644 index 00000000000..34f686a3c25 --- /dev/null +++ b/packages/api-plugin-fulfillment-type-pickup/src/i18n/index.js @@ -0,0 +1,12 @@ +import en from "./en.json"; + +// +// we want all the files in individual +// imports for easier handling by +// automated translation software +// +export default { + translations: [ + ...en + ] +}; diff --git a/packages/api-plugin-fulfillment-type-pickup/src/index.js b/packages/api-plugin-fulfillment-type-pickup/src/index.js index 40f27edacdf..48dd97f7559 100644 --- a/packages/api-plugin-fulfillment-type-pickup/src/index.js +++ b/packages/api-plugin-fulfillment-type-pickup/src/index.js @@ -1,4 +1,5 @@ import { createRequire } from "module"; +import i18n from "./i18n/index.js"; import schemas from "./schemas/index.js"; import startup from "./startup.js"; @@ -15,6 +16,7 @@ export default async function register(app) { label: "Fulfillment Type Pickup", name: "fulfillment-type-pickup", version: pkg.version, + i18n, graphQL: { schemas }, diff --git a/packages/api-plugin-fulfillment-type-shipping/src/i18n/ar.json b/packages/api-plugin-fulfillment-type-shipping/src/i18n/ar.json new file mode 100644 index 00000000000..bb9389c2632 --- /dev/null +++ b/packages/api-plugin-fulfillment-type-shipping/src/i18n/ar.json @@ -0,0 +1,81 @@ +[{ + "i18n": "ar", + "ns": "reaction-shipping", + "translation": { + "reaction-shipping": { + "admin": { + "dashboard": { + "shippingLabel": "الشحن", + "shippingTitle": "الشحن", + "shippingDescription": "توفير معدلات الشحن" + }, + "settings": { + "shippingLabel": "الشحن" + } + }, + "checkoutShipping": { + "selectShippingOption": "حدد خيار الشحن", + "noShippingMethods": "يتم تكوين أي وسائل النقل البحري.", + "contactAdmin": "يرجى الاتصال بمسؤول المتجر.", + "configureNow": "تكوين الآن.", + "shipping": "الشحن" + }, + "shipping": { + "addShippingProvider": "إضافة مزود الشحن", + "editShippingProvider": "تحرير مزود الشحن", + "addShippingMethod": "إضافة وسيلة النقل البحري", + "editShippingMethod": "تعديل طريقة الشحن", + "deleteShippingMethod": "حذف طريقة الشحن", + "noSettingsForThisView": "أي إعدادات لهذا الرأي", + "noShippingMethods": "يتم تكوين أي وسائل النقل البحري.", + "removeShippingMethodConfirm": "هل أنت متأكد أنك تريد حذف {{method}}؟", + "removeShippingMethodTitle": "إزالة طريقة شحن", + "shippingMethodDeleted": "لقد تم حذف هذه طريقة الشحن.", + "removeShippingProvider": "إزالة الشحن موفر", + "removeShippingProviderConfirm": "هل أنت متأكد أنك تريد حذف {{provider}}؟", + "shippingProviderSaved": "مزود الشحن حفظها.", + "shippingProviderUpdated": "تجديد البيانات مزود الشحن.", + "shippingMethodRateAdded": "وأضاف معدل طريقة الشحن.", + "shippingMethodRateUpdated": "الشحن معدل طريقة تحديثها.", + "name": "اسم", + "label": "ملصق", + "group": "مجموعة", + "cost": "كلفة", + "handling": "معالجة", + "rate": "معدل", + "enabled": "تمكين", + "disabled": "تعطيل", + "addRate": "إضافة معدل", + "updateRate": "تحديث معدل {{name}}", + "addNewCondition": "إضافة حالة جديدة", + "deleteCondition": "حذف شرط", + "provider": { + "name": "رمز الخدمة", + "label": "تسمية العامة", + "enabled": "تمكين" + } + }, + "shippingMethod": { + "name": "اسم الطريقة", + "label": "تسمية العامة", + "group": "مجموعة", + "cost": "كلفة", + "handling": "معالجة", + "rate": "معدل", + "enabled": "تمكين", + "matchingCartRanges": "مطابقة نطاقات العربة", + "validRanges": { + "begin": "ابدأ", + "end": "النهاية" + }, + "matchingLocales": "مطابقة لغات", + "validLocales": { + "origination": "من عند", + "destination": "إلى", + "deliveryBegin": "مؤسسة النقل البحري.", + "deliveryEnd": "مؤسسة التسليم." + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-type-shipping/src/i18n/bg.json b/packages/api-plugin-fulfillment-type-shipping/src/i18n/bg.json new file mode 100644 index 00000000000..7ad363c01c2 --- /dev/null +++ b/packages/api-plugin-fulfillment-type-shipping/src/i18n/bg.json @@ -0,0 +1,81 @@ +[{ + "i18n": "bg", + "ns": "reaction-shipping", + "translation": { + "reaction-shipping": { + "admin": { + "dashboard": { + "shippingLabel": "Доставка", + "shippingTitle": "Доставка", + "shippingDescription": "Осигуряване на тарифите за доставка" + }, + "settings": { + "shippingLabel": "Доставка" + } + }, + "checkoutShipping": { + "selectShippingOption": "Изберете опция за доставка", + "noShippingMethods": "Не са методи на доставка са конфигурирани.", + "contactAdmin": "Моля, свържете се с администратора на магазина.", + "configureNow": "Конфигуриране на предприятието.", + "shipping": "Доставка" + }, + "shipping": { + "addShippingProvider": "Добави доставчик корабоплаването", + "editShippingProvider": "Edit доставчик корабоплаването", + "addShippingMethod": "Добави начин за доставка", + "editShippingMethod": "метод Edit корабоплаването", + "deleteShippingMethod": "Изтриване на начин за доставка", + "noSettingsForThisView": "Не настройки за тази гледка", + "noShippingMethods": "Не са методи на доставка са конфигурирани.", + "removeShippingMethodConfirm": "Сигурни ли сте, че искате да изтриете {{method}}?", + "removeShippingMethodTitle": "Премахване Начин на доставка", + "shippingMethodDeleted": "Този метод на пратката е била изтрита.", + "removeShippingProvider": "Премахване Provider Доставка", + "removeShippingProviderConfirm": "Сигурни ли сте, че искате да изтриете {{provider}}?", + "shippingProviderSaved": "доставчик Доставка спасен.", + "shippingProviderUpdated": "актуализирани данни на доставчика за доставка.", + "shippingMethodRateAdded": "прибавя процент Доставка метод.", + "shippingMethodRateUpdated": "ставка метод за доставка се обновява.", + "name": "Име", + "label": "Етикет", + "group": "група", + "cost": "цена", + "handling": "боравене", + "rate": "скорост", + "enabled": "Enabled", + "disabled": "хора с увреждания", + "addRate": "Добави скорост", + "updateRate": "Актуализация {{name}} скорост", + "addNewCondition": "Добавяне на нов състояние", + "deleteCondition": "Изтриване състояние", + "provider": { + "name": "Service Code", + "label": "Public Label", + "enabled": "Enabled" + } + }, + "shippingMethod": { + "name": "Име на метода", + "label": "Public Label", + "group": "група", + "cost": "цена", + "handling": "боравене", + "rate": "скорост", + "enabled": "Enabled", + "matchingCartRanges": "Съвпадение Количка диапазоните", + "validRanges": { + "begin": "започвам", + "end": "Край" + }, + "matchingLocales": "съвпадение Локалите", + "validLocales": { + "origination": "от", + "destination": "Да се", + "deliveryBegin": "Доставка Est.", + "deliveryEnd": "Доставка Est." + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-type-shipping/src/i18n/cs.json b/packages/api-plugin-fulfillment-type-shipping/src/i18n/cs.json new file mode 100644 index 00000000000..cf713e46963 --- /dev/null +++ b/packages/api-plugin-fulfillment-type-shipping/src/i18n/cs.json @@ -0,0 +1,81 @@ +[{ + "i18n": "cs", + "ns": "reaction-shipping", + "translation": { + "reaction-shipping": { + "admin": { + "dashboard": { + "shippingLabel": "lodní", + "shippingTitle": "lodní", + "shippingDescription": "Poskytnout sazby za dopravu" + }, + "settings": { + "shippingLabel": "lodní" + } + }, + "checkoutShipping": { + "selectShippingOption": "Vyberte možnost lodní", + "noShippingMethods": "Žádné způsoby dopravy jsou konfigurovány.", + "contactAdmin": "Obraťte se na správce obchodu.", + "configureNow": "Nakofigurujte.", + "shipping": "lodní" + }, + "shipping": { + "addShippingProvider": "Přidat provozovatele přepravní", + "editShippingProvider": "Editovat poskytovatel lodní", + "addShippingMethod": "Přidat způsob dopravy", + "editShippingMethod": "Upravit způsob dopravy", + "deleteShippingMethod": "Smazat způsob dopravy", + "noSettingsForThisView": "Žádná nastavení pro tento názor", + "noShippingMethods": "Žádné způsoby dopravy jsou konfigurovány.", + "removeShippingMethodConfirm": "Jste si jisti, že chcete smazat {{method}}?", + "removeShippingMethodTitle": "Odstraňte Způsob dopravy", + "shippingMethodDeleted": "Tento způsob dopravy byla smazána.", + "removeShippingProvider": "Odstraňte Shipping Provider", + "removeShippingProviderConfirm": "Jste si jisti, že chcete smazat {{provider}}?", + "shippingProviderSaved": "Poskytovatel vodní doprava uložen.", + "shippingProviderUpdated": "Data Provider vodní doprava aktualizována.", + "shippingMethodRateAdded": "Způsob dopravy rychlost přidán.", + "shippingMethodRateUpdated": "Způsob dopravy rychlost aktualizován.", + "name": "Název", + "label": "Označení", + "group": "Skupina", + "cost": "Náklady", + "handling": "Zacházení", + "rate": "Rychlost", + "enabled": "Povoleno", + "disabled": "invalidní", + "addRate": "Přidejte míru", + "updateRate": "Aktualizace {{name}} rychlost", + "addNewCondition": "Přidat novou podmínku", + "deleteCondition": "smazat podmínku", + "provider": { + "name": "Service Code", + "label": "veřejné Label", + "enabled": "Povoleno" + } + }, + "shippingMethod": { + "name": "Jméno metoda", + "label": "veřejné Label", + "group": "Skupina", + "cost": "Náklady", + "handling": "Zacházení", + "rate": "Rychlost", + "enabled": "Povoleno", + "matchingCartRanges": "Odpovídající košík rozsahy", + "validRanges": { + "begin": "Začít", + "end": "Konec" + }, + "matchingLocales": "odpovídající místního prostredí", + "validLocales": { + "origination": "Od", + "destination": "Na", + "deliveryBegin": "Vodní doprava Est.", + "deliveryEnd": "Dodávka Est." + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-type-shipping/src/i18n/de.json b/packages/api-plugin-fulfillment-type-shipping/src/i18n/de.json new file mode 100644 index 00000000000..ecabec831e6 --- /dev/null +++ b/packages/api-plugin-fulfillment-type-shipping/src/i18n/de.json @@ -0,0 +1,81 @@ +[{ + "i18n": "de", + "ns": "reaction-shipping", + "translation": { + "reaction-shipping": { + "admin": { + "dashboard": { + "shippingLabel": "Versand", + "shippingTitle": "Versand", + "shippingDescription": "Geben Sie Versandkosten" + }, + "settings": { + "shippingLabel": "Versand" + } + }, + "checkoutShipping": { + "selectShippingOption": "Wählen Versandoption", + "noShippingMethods": "Keine Versandmethoden konfiguriert sind.", + "contactAdmin": "Bitte wenden Sie sich an den Ladenverwalter.", + "configureNow": "Jetzt konfigurieren.", + "shipping": "Versand" + }, + "shipping": { + "addShippingProvider": "Versandkosten Anbieter", + "editShippingProvider": "Bearbeiten Versandanbieter", + "addShippingMethod": "In Verfahren Versand", + "editShippingMethod": "Bearbeiten Versandart", + "deleteShippingMethod": "Löschen Versandart", + "noSettingsForThisView": "Keine Einstellungen für diese Ansicht", + "noShippingMethods": "Keine Versandmethoden konfiguriert sind.", + "removeShippingMethodConfirm": "Sind Sie sicher, dass Sie {{method}} löschen?", + "removeShippingMethodTitle": "Entfernen Sie Versandart", + "shippingMethodDeleted": "Diese Versandart wurde gelöscht.", + "removeShippingProvider": "Entfernen Sie Liefer-Provider", + "removeShippingProviderConfirm": "Sind Sie sicher, dass Sie {{provider}} löschen?", + "shippingProviderSaved": "Versandanbieters gespeichert.", + "shippingProviderUpdated": "Versand-Provider-Daten aktualisiert.", + "shippingMethodRateAdded": "Versandart Rate hinzugefügt.", + "shippingMethodRateUpdated": "Versandart Rate aktualisiert.", + "name": "Name", + "label": "Etikette", + "group": "Gruppe", + "cost": "Kosten", + "handling": "Handhabung", + "rate": "Preis", + "enabled": "Aktiviert", + "disabled": "Behindert", + "addRate": "In Rate", + "updateRate": "Update-Rate {{name}}", + "addNewCondition": "Hinzufügen neuer Zustand", + "deleteCondition": "Bedingung löschen", + "provider": { + "name": "Servicecode", + "label": "Öffentliche Etikett", + "enabled": "Aktiviert" + } + }, + "shippingMethod": { + "name": "Methodenname", + "label": "Öffentliche Etikett", + "group": "Gruppe", + "cost": "Kosten", + "handling": "Handhabung", + "rate": "Preis", + "enabled": "Aktiviert", + "matchingCartRanges": "Passende Wagen Ranges", + "validRanges": { + "begin": "Start", + "end": "Ende" + }, + "matchingLocales": "Passende Locales", + "validLocales": { + "origination": "Von", + "destination": "Bis", + "deliveryBegin": "Versand Est.", + "deliveryEnd": "Lieferung Est." + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-type-shipping/src/i18n/el.json b/packages/api-plugin-fulfillment-type-shipping/src/i18n/el.json new file mode 100644 index 00000000000..97fb9d68a78 --- /dev/null +++ b/packages/api-plugin-fulfillment-type-shipping/src/i18n/el.json @@ -0,0 +1,81 @@ +[{ + "i18n": "el", + "ns": "reaction-shipping", + "translation": { + "reaction-shipping": { + "admin": { + "dashboard": { + "shippingLabel": "Αποστολή", + "shippingTitle": "Αποστολή", + "shippingDescription": "Παρέχουν τα έξοδα αποστολής" + }, + "settings": { + "shippingLabel": "Αποστολή" + } + }, + "checkoutShipping": { + "selectShippingOption": "Επιλέξτε την επιλογή αποστολής", + "noShippingMethods": "Δεν μέθοδοι ναυτιλίας ρυθμιστεί.", + "contactAdmin": "Επικοινωνήστε με τον διαχειριστή του καταστήματος.", + "configureNow": "Διαμορφώστε τώρα.", + "shipping": "Αποστολή" + }, + "shipping": { + "addShippingProvider": "Προσθέστε την υπηρεσία αποστολής", + "editShippingProvider": "Επεξεργασία υπηρεσία αποστολής", + "addShippingMethod": "Προσθήκη μέθοδο αποστολής", + "editShippingMethod": "Επεξεργασία μεθόδου αποστολής", + "deleteShippingMethod": "Διαγραφή μέθοδο αποστολής", + "noSettingsForThisView": "Δεν υπάρχουν ρυθμίσεις για αυτό το σκοπό", + "noShippingMethods": "Δεν μέθοδοι ναυτιλίας ρυθμιστεί.", + "removeShippingMethodConfirm": "Είστε σίγουροι ότι θέλετε να διαγράψετε {{method}};", + "removeShippingMethodTitle": "Κατάργηση Μέθοδος ναυτιλίας", + "shippingMethodDeleted": "Αυτή η μέθοδος ναυτιλίας έχει διαγραφεί.", + "removeShippingProvider": "Κατάργηση Provider Ναυτιλίας", + "removeShippingProviderConfirm": "Είστε σίγουροι ότι θέλετε να διαγράψετε {{provider}};", + "shippingProviderSaved": "Ο πάροχος υπηρεσιών αποστολής σωθεί.", + "shippingProviderUpdated": "ενημερωμένα δεδομένα παρόχου ναυτιλία.", + "shippingMethodRateAdded": "ρυθμός μέθοδο αποστολής πρόσθεσε.", + "shippingMethodRateUpdated": "ρυθμός μέθοδο αποστολής ενημερωθεί.", + "name": "Όνομα", + "label": "Επιγραφή", + "group": "Ομάδα", + "cost": "Κόστος", + "handling": "Χειριζόμενος", + "rate": "Τιμή", + "enabled": "Ενεργοποιήθηκε", + "disabled": "Ανάπηρος", + "addRate": "Προσθέστε ποσοστό", + "updateRate": "ρυθμό ανανέωσης {{name}}", + "addNewCondition": "Προσθήκη νέου κατάσταση", + "deleteCondition": "Διαγραφή κατάσταση", + "provider": { + "name": "Κωδικός υπηρεσίας", + "label": "δημόσια Label", + "enabled": "Ενεργοποιήθηκε" + } + }, + "shippingMethod": { + "name": "Όνομα μέθοδο", + "label": "δημόσια Label", + "group": "Ομάδα", + "cost": "Κόστος", + "handling": "Χειριζόμενος", + "rate": "Τιμή", + "enabled": "Ενεργοποιήθηκε", + "matchingCartRanges": "Ταιριάζουν καλαθιού Σειρές", + "validRanges": { + "begin": "Αρχίζουν", + "end": "Τέλος" + }, + "matchingLocales": "ταιριάζουν Τοπικές", + "validLocales": { + "origination": "Από", + "destination": "Να", + "deliveryBegin": "Αποστολές Est.", + "deliveryEnd": "Παράδοση Est." + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-type-shipping/src/i18n/en.json b/packages/api-plugin-fulfillment-type-shipping/src/i18n/en.json new file mode 100644 index 00000000000..cbd127afaac --- /dev/null +++ b/packages/api-plugin-fulfillment-type-shipping/src/i18n/en.json @@ -0,0 +1,84 @@ +[{ + "i18n": "en", + "ns": "reaction-shipping", + "translation": { + "reaction-shipping": { + "admin": { + "dashboard": { + "shippingLabel": "Shipping", + "shippingTitle": "Shipping", + "shippingDescription": "Provide shipping rates" + }, + "settings": { + "shippingLabel": "Shipping" + } + }, + "checkoutShipping": { + "selectShippingOption": "Select shipping option", + "noShippingMethods": "No shipping methods are configured.", + "contactAdmin": "Please contact the store administrator.", + "configureNow": "Configure now.", + "shipping": "Shipping" + }, + "shipping": { + "addShippingProvider": "Add shipping provider", + "editShippingProvider": "Edit shipping provider", + "addShippingMethod": "Add shipping method", + "editShippingMethod": "Edit shipping method", + "deleteShippingMethod": "Delete shipping method", + "noSettingsForThisView": "No settings for this view", + "noShippingMethods": "No shipping methods are configured.", + "removeShippingMethodConfirm": "Are you sure you want to delete {{method}}?", + "removeShippingMethodTitle": "Remove Shipping Method", + "shippingMethodDeleted": "This shipping method has been deleted.", + "removeShippingProvider": "Remove Shipping Provider", + "removeShippingProviderConfirm": "Are you sure you want to delete {{provider}}?", + "shippingProviderSaved": "Shipping provider saved.", + "shippingProviderUpdated": "Shipping provider data updated.", + "shippingMethodRateAdded": "Shipping method rate added.", + "shippingMethodRateUpdated": "Shipping method rate updated.", + "name": "Name", + "label": "Label", + "group": "Group", + "cost": "Cost", + "handling": "Handling", + "rate": "Rate", + "enabled": "Enabled", + "disabled": "Disabled", + "addRate": "Add rate", + "updateRate": "Update {{name}} rate", + "addNewCondition": "Add new condition", + "deleteCondition": "Delete condition", + "provider": { + "name": "Service Code", + "label": "Public Label", + "enabled": "Enabled" + } + }, + "shippingMethod": { + "name": "Method Name", + "label": "Public Label", + "group": "Group", + "cost": "Cost", + "handling": "Handling", + "rate": "Rate", + "enabled": "Enabled", + "matchingCartRanges": "Matching Cart Ranges", + "validRanges": { + "begin": "Begin", + "end": "End" + }, + "matchingLocales": "Matching Locales", + "validLocales": { + "origination": "From", + "destination": "To", + "deliveryBegin": "Shipping Est.", + "deliveryEnd": "Delivery Est." + } + }, + "defaultParcelSize": { + "label": "Default Parcel Size" + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-type-shipping/src/i18n/es.json b/packages/api-plugin-fulfillment-type-shipping/src/i18n/es.json new file mode 100644 index 00000000000..22ac7faeed4 --- /dev/null +++ b/packages/api-plugin-fulfillment-type-shipping/src/i18n/es.json @@ -0,0 +1,87 @@ +[{ + "i18n": "es", + "ns": "reaction-shipping", + "translation": { + "reaction-shipping": { + "admin": { + "dashboard": { + "shippingLabel": "Envío", + "shippingTitle": "Envío", + "shippingDescription": "Indicar gastos de envío" + }, + "settings": { + "shippingLabel": "Envío" + }, + "shippingSettings": { + "header": "Envío" + } + }, + "checkoutShipping": { + "selectShippingOption": "Seleccione la opción de envío", + "noShippingMethods": "No hay métodos de envío están configurados.", + "contactAdmin": "Póngase en contacto con el administrador de la tienda.", + "configureNow": "Configurar ahora.", + "shipping": "Envío" + }, + "shipping": { + "addShippingProvider": "Añadir la empresa de transportes", + "editShippingProvider": "Editar proveedor de envío", + "addShippingMethod": "Añadir método de envío", + "editShippingMethod": "Editar método de envío", + "deleteShippingMethod": "Eliminar método de envío", + "noSettingsForThisView": "No hay parámetros para este punto de vista", + "noShippingMethods": "No hay métodos de envío están configurados.", + "removeShippingMethodConfirm": "¿Está seguro de que quiere eliminar {{method}}?", + "removeShippingMethodTitle": "Retire Método de envío", + "shippingMethodDeleted": "Este método de envío que se ha suprimido.", + "removeShippingProvider": "Retire Proveedor de envío", + "removeShippingProviderConfirm": "¿Está seguro de que quiere eliminar {{provider}}?", + "shippingProviderSaved": "Formas de envío salvó.", + "shippingProviderUpdated": "Formas de envío de datos actualizados.", + "shippingMethodRateAdded": "tasa método de envío añadió.", + "shippingMethodRateUpdated": "tasa de actualización del método de envío.", + "name": "Nombre", + "label": "Etiqueta", + "group": "Grupo", + "cost": "Costo", + "handling": "Manejo", + "rate": "Tarifa", + "enabled": "Activado", + "disabled": "Discapacitados", + "addRate": "Añadir tasa", + "updateRate": "Velocidad de actualización {{name}}", + "addNewCondition": "Añadir nueva condición", + "deleteCondition": "eliminar condición", + "provider": { + "name": "Código de servicio", + "label": "Etiqueta pública", + "enabled": "Activado" + } + }, + "shippingMethod": { + "name": "Nombre del método", + "label": "Etiqueta pública", + "group": "Grupo", + "cost": "Costo", + "handling": "Manejo", + "rate": "Tarifa", + "enabled": "Activado", + "matchingCartRanges": "Coincidencia Cesta Gamas", + "validRanges": { + "begin": "Empezar", + "end": "Fin" + }, + "matchingLocales": "Locales a juego", + "validLocales": { + "origination": "De", + "destination": "A", + "deliveryBegin": "Este envío.", + "deliveryEnd": "Entrega Est." + } + }, + "defaultParcelSize": { + "label": "Tamaño por defecto del paquete" + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-type-shipping/src/i18n/fr.json b/packages/api-plugin-fulfillment-type-shipping/src/i18n/fr.json new file mode 100644 index 00000000000..da7fa9e3c7e --- /dev/null +++ b/packages/api-plugin-fulfillment-type-shipping/src/i18n/fr.json @@ -0,0 +1,81 @@ +[{ + "i18n": "fr", + "ns": "reaction-shipping", + "translation": { + "reaction-shipping": { + "admin": { + "dashboard": { + "shippingLabel": "Livraison", + "shippingTitle": "Livraison", + "shippingDescription": "Gérer les frais de port" + }, + "settings": { + "shippingLabel": "Livraison" + } + }, + "checkoutShipping": { + "selectShippingOption": "Sélectionnez une option d'expédition", + "noShippingMethods": "Aucune méthode d'expédition n'est configurée.", + "contactAdmin": "Veuillez contacter l'administrateur de la boutique.", + "configureNow": "Configurer maintenant.", + "shipping": "Livraison" + }, + "shipping": { + "addShippingProvider": "Ajouter un transporteur", + "editShippingProvider": "Modifier le transporteur", + "addShippingMethod": "Ajouter un mode de livraison", + "editShippingMethod": "Modifier le mode de livraison", + "deleteShippingMethod": "Supprimer le mode de livraison", + "noSettingsForThisView": "Pas de paramètres pour cette vue", + "noShippingMethods": "Aucune méthode d'expédition n'est configurée.", + "removeShippingMethodConfirm": "Êtes-vous sûr de vouloir supprimer {{method}} ?", + "removeShippingMethodTitle": "Supprimer le mode de livraison", + "shippingMethodDeleted": "Ce mode de livraison a été supprimé.", + "removeShippingProvider": "Supprimer le transporteur", + "removeShippingProviderConfirm": "Êtes-vous sûr de vouloir supprimer {{provider}}?", + "shippingProviderSaved": "Mode de livraison sauvegardé.", + "shippingProviderUpdated": "Données du fournisseur de livraison mises à jour.", + "shippingMethodRateAdded": "Tarif de livraison ajouté.", + "shippingMethodRateUpdated": "Tarif de livraison mis à jour.", + "name": "Nom", + "label": "Étiquette", + "group": "Groupe", + "cost": "Coût", + "handling": "Prise en charge", + "rate": "Taux", + "enabled": "Activé", + "disabled": "Désactivé", + "addRate": "Ajouter tarif", + "updateRate": "Mettre à jour le tarif {{name}}", + "addNewCondition": "Ajouter un nouvel état", + "deleteCondition": "Supprimer l'état", + "provider": { + "name": "Référence interne", + "label": "Nom public", + "enabled": "Activé" + } + }, + "shippingMethod": { + "name": "Référence interne", + "label": "Nom public", + "group": "Groupe", + "cost": "Coût", + "handling": "Prise en charge", + "rate": "Taux", + "enabled": "Activé", + "matchingCartRanges": "Fourchette de panier correspondante", + "validRanges": { + "begin": "Début", + "end": "Fin" + }, + "matchingLocales": "Localités correspondantes", + "validLocales": { + "origination": "Au départ de", + "destination": "Vers", + "deliveryBegin": "Estimation des frais de port", + "deliveryEnd": "Livraison estimée" + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-type-shipping/src/i18n/he.json b/packages/api-plugin-fulfillment-type-shipping/src/i18n/he.json new file mode 100644 index 00000000000..a962347f7d5 --- /dev/null +++ b/packages/api-plugin-fulfillment-type-shipping/src/i18n/he.json @@ -0,0 +1,5 @@ +[{ + "i18n": "he", + "ns": "reaction-shipping", + "translation": { } +}] diff --git a/packages/api-plugin-fulfillment-type-shipping/src/i18n/hr.json b/packages/api-plugin-fulfillment-type-shipping/src/i18n/hr.json new file mode 100644 index 00000000000..04b190e24bc --- /dev/null +++ b/packages/api-plugin-fulfillment-type-shipping/src/i18n/hr.json @@ -0,0 +1,81 @@ +[{ + "i18n": "hr", + "ns": "reaction-shipping", + "translation": { + "reaction-shipping": { + "admin": { + "dashboard": { + "shippingLabel": "dostava", + "shippingTitle": "dostava", + "shippingDescription": "Osigurati cijene dostave" + }, + "settings": { + "shippingLabel": "dostava" + } + }, + "checkoutShipping": { + "selectShippingOption": "Odaberite opciju dostave", + "noShippingMethods": "Nema načina dostave konfigurirane.", + "contactAdmin": "Obratite se administratoru trgovine.", + "configureNow": "Konfiguracija sada.", + "shipping": "dostava" + }, + "shipping": { + "addShippingProvider": "Dodaj dostave davatelja", + "editShippingProvider": "Uredi ponuđač dostave", + "addShippingMethod": "Dodaj način dostave", + "editShippingMethod": "Uredite način dostave", + "deleteShippingMethod": "Brisanje način dostave", + "noSettingsForThisView": "Nema postavki za ovu pogled", + "noShippingMethods": "Nema načina dostave konfigurirane.", + "removeShippingMethodConfirm": "Jeste li sigurni da želite obrisati {{method}}?", + "removeShippingMethodTitle": "Ukloni Način dostave", + "shippingMethodDeleted": "Ova metoda dostave je obrisan.", + "removeShippingProvider": "Ukloni Dostava Provider", + "removeShippingProviderConfirm": "Jeste li sigurni da želite obrisati {{provider}}?", + "shippingProviderSaved": "Dostava davatelj spasio.", + "shippingProviderUpdated": "Dostava davatelja podataka ažurirati.", + "shippingMethodRateAdded": "Dostava stopa metoda dodan.", + "shippingMethodRateUpdated": "Dostava stopa metoda ažuriran.", + "name": "Ime", + "label": "Označiti", + "group": "Skupina", + "cost": "cijena", + "handling": "Rukovanje", + "rate": "Stopa", + "enabled": "Omogućeno", + "disabled": "onesposobljen", + "addRate": "Dodaj stopu", + "updateRate": "Ažuriranje {{name}} stopa", + "addNewCondition": "Dodaj novo stanje", + "deleteCondition": "Brisanje stanje", + "provider": { + "name": "kod usluga", + "label": "Javni Label", + "enabled": "Omogućeno" + } + }, + "shippingMethod": { + "name": "Naziv metoda", + "label": "Javni Label", + "group": "Skupina", + "cost": "cijena", + "handling": "Rukovanje", + "rate": "Stopa", + "enabled": "Omogućeno", + "matchingCartRanges": "Odgovarajući košarice rasponi", + "validRanges": { + "begin": "Početi", + "end": "Kraj" + }, + "matchingLocales": "odgovarajući locales", + "validLocales": { + "origination": "Iz", + "destination": "Do", + "deliveryBegin": "Dostava Est.", + "deliveryEnd": "Isporuka Est." + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-type-shipping/src/i18n/hu.json b/packages/api-plugin-fulfillment-type-shipping/src/i18n/hu.json new file mode 100644 index 00000000000..c090a42a9ca --- /dev/null +++ b/packages/api-plugin-fulfillment-type-shipping/src/i18n/hu.json @@ -0,0 +1,81 @@ +[{ + "i18n": "hu", + "ns": "reaction-shipping", + "translation": { + "reaction-shipping": { + "admin": { + "dashboard": { + "shippingLabel": "Szállítás", + "shippingTitle": "Szállítás", + "shippingDescription": "Adjon szállítási díjak" + }, + "settings": { + "shippingLabel": "Szállítás" + } + }, + "checkoutShipping": { + "selectShippingOption": "Válassza ki a szállítási opciót", + "noShippingMethods": "Nincs szállítási módszerek vannak beállítva.", + "contactAdmin": "Kérjük, forduljon a bolt rendszergazda.", + "configureNow": "Konfigurálása most.", + "shipping": "Szállítás" + }, + "shipping": { + "addShippingProvider": "Add szállítási szolgáltató", + "editShippingProvider": "Szállítás szerkesztése szolgáltató", + "addShippingMethod": "Add szállítási mód", + "editShippingMethod": "Szállítási mód szerkesztése", + "deleteShippingMethod": "Törlés szállítási mód", + "noSettingsForThisView": "Nincs beállítás ehhez a nézethez", + "noShippingMethods": "Nincs szállítási módszerek vannak beállítva.", + "removeShippingMethodConfirm": "Biztosan törölni szeretné {{method}}?", + "removeShippingMethodTitle": "Távolítsuk Szállítás módja", + "shippingMethodDeleted": "Ez a szállítási mód törölték.", + "removeShippingProvider": "Távolítsuk el a Szállítási Szolgáltató", + "removeShippingProviderConfirm": "Biztosan törölni szeretné {{provider}}?", + "shippingProviderSaved": "Szállítási szolgáltató mentve.", + "shippingProviderUpdated": "Szállítási szolgáltató adatai módosítva.", + "shippingMethodRateAdded": "Szállítási mód arány hozzá.", + "shippingMethodRateUpdated": "Szállítási mód sebesség frissíteni.", + "name": "Név", + "label": "Címke", + "group": "Csoport", + "cost": "Költség", + "handling": "Kezelése", + "rate": "Arány", + "enabled": "Engedélyezett", + "disabled": "Tiltva", + "addRate": "Add ráta", + "updateRate": "Frissítés {{name}} ráta", + "addNewCondition": "Új állapot", + "deleteCondition": "Törlés állapotban", + "provider": { + "name": "Service Code", + "label": "Public Label", + "enabled": "Engedélyezett" + } + }, + "shippingMethod": { + "name": "módszer neve", + "label": "Public Label", + "group": "Csoport", + "cost": "Költség", + "handling": "Kezelése", + "rate": "Arány", + "enabled": "Engedélyezett", + "matchingCartRanges": "Matching Cart tartományok", + "validRanges": { + "begin": "Kezdődik", + "end": "vég" + }, + "matchingLocales": "Matching Nyelv", + "validLocales": { + "origination": "Ból ből", + "destination": "Nak nek", + "deliveryBegin": "Szállítási Est.", + "deliveryEnd": "Szállítási Est." + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-type-shipping/src/i18n/index.js b/packages/api-plugin-fulfillment-type-shipping/src/i18n/index.js new file mode 100644 index 00000000000..9deb0a6b6e7 --- /dev/null +++ b/packages/api-plugin-fulfillment-type-shipping/src/i18n/index.js @@ -0,0 +1,54 @@ +import ar from "./ar.json"; +import bg from "./bg.json"; +import de from "./de.json"; +import el from "./el.json"; +import en from "./en.json"; +import es from "./es.json"; +import fr from "./fr.json"; +import he from "./he.json"; +import hr from "./hr.json"; +import it from "./it.json"; +import my from "./my.json"; +import nb from "./nb.json"; +import nl from "./nl.json"; +import pl from "./pl.json"; +import pt from "./pt.json"; +import ro from "./ro.json"; +import ru from "./ru.json"; +import sl from "./sl.json"; +import sv from "./sv.json"; +import tr from "./tr.json"; +import vi from "./vi.json"; +import zh from "./zh.json"; + +// +// we want all the files in individual +// imports for easier handling by +// automated translation software +// +export default { + translations: [ + ...ar, + ...bg, + ...de, + ...el, + ...en, + ...es, + ...fr, + ...he, + ...hr, + ...it, + ...my, + ...nb, + ...nl, + ...pl, + ...pt, + ...ro, + ...ru, + ...sl, + ...sv, + ...tr, + ...vi, + ...zh + ] +}; diff --git a/packages/api-plugin-fulfillment-type-shipping/src/i18n/it.json b/packages/api-plugin-fulfillment-type-shipping/src/i18n/it.json new file mode 100644 index 00000000000..e47cf932b82 --- /dev/null +++ b/packages/api-plugin-fulfillment-type-shipping/src/i18n/it.json @@ -0,0 +1,81 @@ +[{ + "i18n": "it", + "ns": "reaction-shipping", + "translation": { + "reaction-shipping": { + "admin": { + "dashboard": { + "shippingLabel": "spedizione", + "shippingTitle": "spedizione", + "shippingDescription": "Fornire le tariffe di spedizione" + }, + "settings": { + "shippingLabel": "spedizione" + } + }, + "checkoutShipping": { + "selectShippingOption": "Selezionare l'opzione di spedizione", + "noShippingMethods": "No Metodi di spedizione sono configurati.", + "contactAdmin": "Contattare l'amministratore del negozio.", + "configureNow": "Configurare ora.", + "shipping": "spedizione" + }, + "shipping": { + "addShippingProvider": "Aggiungere corriere", + "editShippingProvider": "Fornitore di trasporto Modifica", + "addShippingMethod": "Aggiungere il metodo di spedizione", + "editShippingMethod": "Modifica metodo di spedizione", + "deleteShippingMethod": "Elimina metodo di spedizione", + "noSettingsForThisView": "Nessuna impostazione per questo punto di vista", + "noShippingMethods": "No Metodi di spedizione sono configurati.", + "removeShippingMethodConfirm": "Sei sicuro di voler eliminare {{method}}?", + "removeShippingMethodTitle": "Rimuovere Metodo di Spedizione", + "shippingMethodDeleted": "Questo metodo di trasporto è stato eliminato.", + "removeShippingProvider": "Rimuovere Spedizione Provider", + "removeShippingProviderConfirm": "Sei sicuro di voler eliminare {{provider}}?", + "shippingProviderSaved": "Fornitore di trasporto salvato.", + "shippingProviderUpdated": "i dati del provider Spedizione aggiornati.", + "shippingMethodRateAdded": "tasso Metodo di trasporto aggiunto.", + "shippingMethodRateUpdated": "tasso Metodo di trasporto aggiornato.", + "name": "Nome", + "label": "Etichetta", + "group": "Gruppo", + "cost": "Costo", + "handling": "maneggio", + "rate": "Tasso", + "enabled": "Abilitato", + "disabled": "Disabilitato", + "addRate": "Aggiungere tasso", + "updateRate": "Velocità di aggiornamento {{name}}", + "addNewCondition": "Aggiungi nuova condizione", + "deleteCondition": "Eliminare condizioni", + "provider": { + "name": "Codice servizio", + "label": "pubblico Label", + "enabled": "Abilitato" + } + }, + "shippingMethod": { + "name": "Nome metodo", + "label": "pubblico Label", + "group": "Gruppo", + "cost": "Costo", + "handling": "maneggio", + "rate": "Tasso", + "enabled": "Abilitato", + "matchingCartRanges": "Abbinamento Carrello Ranges", + "validRanges": { + "begin": "Inizio", + "end": "Fine" + }, + "matchingLocales": "Impostazioni internazionali di corrispondenza", + "validLocales": { + "origination": "Da", + "destination": "A", + "deliveryBegin": "Spedizione Est.", + "deliveryEnd": "Consegna Est." + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-type-shipping/src/i18n/my.json b/packages/api-plugin-fulfillment-type-shipping/src/i18n/my.json new file mode 100644 index 00000000000..58dc97340b9 --- /dev/null +++ b/packages/api-plugin-fulfillment-type-shipping/src/i18n/my.json @@ -0,0 +1,81 @@ +[{ + "i18n": "my", + "ns": "reaction-shipping", + "translation": { + "reaction-shipping": { + "admin": { + "dashboard": { + "shippingLabel": "သင်္ဘောဖြင့်ကုန်ပစ္စည်းပို့ခြင်း", + "shippingTitle": "သင်္ဘောဖြင့်ကုန်ပစ္စည်းပို့ခြင်း", + "shippingDescription": "ရေကြောင်းနှုန်းထားများပေး" + }, + "settings": { + "shippingLabel": "သင်္ဘောဖြင့်ကုန်ပစ္စည်းပို့ခြင်း" + } + }, + "checkoutShipping": { + "selectShippingOption": "ရေကြောင်း option ကို Select လုပ်ပါ", + "noShippingMethods": "အဘယ်သူမျှမရေကြောင်းနည်းလမ်းများ configured ကြသည်။", + "contactAdmin": "စတိုးဆိုင်စီမံခန့်ခွဲသူကိုဆက်သွယ်ပါ။", + "configureNow": "ယခု configure ။", + "shipping": "သင်္ဘောဖြင့်ကုန်ပစ္စည်းပို့ခြင်း" + }, + "shipping": { + "addShippingProvider": "ရေကြောင်းပံ့ပိုးပေး Add", + "editShippingProvider": "Edit ကိုရေကြောင်းပံ့ပိုးပေး", + "addShippingMethod": "ရေကြောင်းနည်းလမ်း Add", + "editShippingMethod": "Edit ကိုရေကြောင်းနည်းလမ်း", + "deleteShippingMethod": "ရေကြောင်းနည်းလမ်းဖျက်ပစ်ပါ", + "noSettingsForThisView": "ဒီအမြင်အဘို့အဘယ်သူမျှမကအပြင်အဆင်များ", + "noShippingMethods": "အဘယ်သူမျှမရေကြောင်းနည်းလမ်းများ configured ကြသည်။", + "removeShippingMethodConfirm": "သငျသညျ {{method}} သင်ဖျက်လိုသည်မှာသေချာပါသလား", + "removeShippingMethodTitle": "သဘောင်္တင်ခ Method ကို Remove", + "shippingMethodDeleted": "ဒီသင်္ဘောနည်းလမ်းဖျက်ထားသည်။", + "removeShippingProvider": "သဘောင်္တင်ခပေးသူ Remove", + "removeShippingProviderConfirm": "သငျသညျ {{provider}} သင်ဖျက်လိုသည်မှာသေချာပါသလား", + "shippingProviderSaved": "သဘောင်္တင်ပံ့ပိုးပေးသူကယ်လွှတ်တော်မူ၏။", + "shippingProviderUpdated": "သဘောင်္တင်ပံ့ပိုးပေးသူဒေတာ updated ။", + "shippingMethodRateAdded": "သဘောင်္တင်ခနည်းလမ်းမှုနှုန်းကဆက်ပြောသည်။", + "shippingMethodRateUpdated": "updated သဘောင်္တင်နည်းလမ်းနှုန်းသည်။", + "name": "နာမကိုအမှီ", + "label": "တံဆိပ်", + "group": "Group က", + "cost": "ပေးရ", + "handling": "ကိုင်တွယ်", + "rate": "rate", + "enabled": "enabled", + "disabled": "မသန်စွမ်း", + "addRate": "နှုန်းမှာ Add", + "updateRate": "Update ကို {{name}} မှုနှုန်း", + "addNewCondition": "အသစ်အခွအေနေ Add", + "deleteCondition": "အခွအေနေဖျက်ပစ်ပါ", + "provider": { + "name": "Service ကို Code ကို", + "label": "ပြည်သူ့တံဆိပ်", + "enabled": "enabled" + } + }, + "shippingMethod": { + "name": "method ကိုအမည်", + "label": "ပြည်သူ့တံဆိပ်", + "group": "Group က", + "cost": "ပေးရ", + "handling": "ကိုင်တွယ်", + "rate": "rate", + "enabled": "enabled", + "matchingCartRanges": "ကိုက်ညီတဲ့လှည်းတောင်တန်း", + "validRanges": { + "begin": "အစ", + "end": "အဆုံး" + }, + "matchingLocales": "ကိုက်ညီတဲ့ဒေသခံ", + "validLocales": { + "origination": "မှ", + "destination": "သို့", + "deliveryBegin": "သဘောင်္တင်ခ Est ။", + "deliveryEnd": "Delivery Est ။" + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-type-shipping/src/i18n/nb.json b/packages/api-plugin-fulfillment-type-shipping/src/i18n/nb.json new file mode 100644 index 00000000000..c90e37d0641 --- /dev/null +++ b/packages/api-plugin-fulfillment-type-shipping/src/i18n/nb.json @@ -0,0 +1,5 @@ +[{ + "i18n": "nb", + "ns": "reaction-shipping", + "translation": { } +}] diff --git a/packages/api-plugin-fulfillment-type-shipping/src/i18n/nl.json b/packages/api-plugin-fulfillment-type-shipping/src/i18n/nl.json new file mode 100644 index 00000000000..eb5a19fe192 --- /dev/null +++ b/packages/api-plugin-fulfillment-type-shipping/src/i18n/nl.json @@ -0,0 +1,81 @@ +[{ + "i18n": "nl", + "ns": "reaction-shipping", + "translation": { + "reaction-shipping": { + "admin": { + "dashboard": { + "shippingLabel": "Verzenden", + "shippingTitle": "Verzenden", + "shippingDescription": "Bieden verzendkosten" + }, + "settings": { + "shippingLabel": "Verzenden" + } + }, + "checkoutShipping": { + "selectShippingOption": "Kies verzendoptie", + "noShippingMethods": "Geen verzendkosten methoden worden geconfigureerd.", + "contactAdmin": "Neem contact op met de winkelbeheerder.", + "configureNow": "Configureren nu.", + "shipping": "Verzenden" + }, + "shipping": { + "addShippingProvider": "Voeg scheepvaart provider", + "editShippingProvider": "Bewerken scheepvaart provider", + "addShippingMethod": "Voeg verzendmethode", + "editShippingMethod": "Bewerken verzendmethode", + "deleteShippingMethod": "Verwijder verzendmethode", + "noSettingsForThisView": "Geen instellingen voor dit standpunt", + "noShippingMethods": "Geen verzendkosten methoden worden geconfigureerd.", + "removeShippingMethodConfirm": "Bent u zeker dat u wilt {{method}} verwijderen?", + "removeShippingMethodTitle": "Verwijder Verzendmethode", + "shippingMethodDeleted": "Deze verzendmethode is verwijderd.", + "removeShippingProvider": "Verwijder Shipping Provider", + "removeShippingProviderConfirm": "Bent u zeker dat u wilt {{provider}} verwijderen?", + "shippingProviderSaved": "Shipping provider opgeslagen.", + "shippingProviderUpdated": "Verzending provider gegevens bijgewerkt.", + "shippingMethodRateAdded": "Verzendmethode snelheid toegevoegd.", + "shippingMethodRateUpdated": "Verzendmethode rate bijgewerkt.", + "name": "Naam", + "label": "Etiket", + "group": "Groep", + "cost": "Kosten", + "handling": "behandeling", + "rate": "Snelheid", + "enabled": "Ingeschakeld", + "disabled": "Gehandicapte", + "addRate": "tarief toe te voegen", + "updateRate": "Update {{name}} rate", + "addNewCondition": "Voeg een nieuwe voorwaarde", + "deleteCondition": "Verwijder conditie", + "provider": { + "name": "service Code", + "label": "openbare Label", + "enabled": "Ingeschakeld" + } + }, + "shippingMethod": { + "name": "methode Naam", + "label": "openbare Label", + "group": "Groep", + "cost": "Kosten", + "handling": "behandeling", + "rate": "Snelheid", + "enabled": "Ingeschakeld", + "matchingCartRanges": "Matching winkelwagen Ranges", + "validRanges": { + "begin": "Beginnen", + "end": "Einde" + }, + "matchingLocales": "matching Locales", + "validLocales": { + "origination": "Van", + "destination": "Naar", + "deliveryBegin": "Shipping Est.", + "deliveryEnd": "Levering Est." + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-type-shipping/src/i18n/pl.json b/packages/api-plugin-fulfillment-type-shipping/src/i18n/pl.json new file mode 100644 index 00000000000..fbbb2d454a1 --- /dev/null +++ b/packages/api-plugin-fulfillment-type-shipping/src/i18n/pl.json @@ -0,0 +1,81 @@ +[{ + "i18n": "pl", + "ns": "reaction-shipping", + "translation": { + "reaction-shipping": { + "admin": { + "dashboard": { + "shippingLabel": "Wysyłka", + "shippingTitle": "Wysyłka", + "shippingDescription": "Zapewnić koszty wysyłki" + }, + "settings": { + "shippingLabel": "Wysyłka" + } + }, + "checkoutShipping": { + "selectShippingOption": "Wybierz opcję wysyłki", + "noShippingMethods": "są skonfigurowane żadne metody wysyłki.", + "contactAdmin": "Skontaktuj się z administratorem sklepu.", + "configureNow": "Skonfiguruj teraz.", + "shipping": "Wysyłka" + }, + "shipping": { + "addShippingProvider": "Dodaj dostawcę przesyłki", + "editShippingProvider": "Edycja kurierska", + "addShippingMethod": "Dodaj metodę wysyłki", + "editShippingMethod": "Zmień sposób wysyłki", + "deleteShippingMethod": "Usuń metodę wysyłki", + "noSettingsForThisView": "Brak ustawienia dla tego widoku", + "noShippingMethods": "są skonfigurowane żadne metody wysyłki.", + "removeShippingMethodConfirm": "Czy na pewno chcesz usunąć {{method}}?", + "removeShippingMethodTitle": "Usuń metody wysyłki", + "shippingMethodDeleted": "Ta metoda wysyłki została usunięta.", + "removeShippingProvider": "Usuń kurierska", + "removeShippingProviderConfirm": "Czy na pewno chcesz usunąć {{provider}}?", + "shippingProviderSaved": "dostawcą Dostawa zapisane.", + "shippingProviderUpdated": "zaktualizowane dane kurierska.", + "shippingMethodRateAdded": "Dostawa stopa metoda dodaje.", + "shippingMethodRateUpdated": "Dostawa stopa metoda aktualizowana.", + "name": "Nazwa", + "label": "Etykieta", + "group": "Grupa", + "cost": "Koszt", + "handling": "Obsługa", + "rate": "Oceniać", + "enabled": "Włączone", + "disabled": "Niepełnosprawny", + "addRate": "Dodaj stopę", + "updateRate": "Częstotliwość odświeżania {{name}}", + "addNewCondition": "Dodaj nowy warunek", + "deleteCondition": "Usuń warunek", + "provider": { + "name": "service Code", + "label": "Etykieta publiczny", + "enabled": "Włączone" + } + }, + "shippingMethod": { + "name": "Nazwa metody", + "label": "Etykieta publiczny", + "group": "Grupa", + "cost": "Koszt", + "handling": "Obsługa", + "rate": "Oceniać", + "enabled": "Włączone", + "matchingCartRanges": "Dopasowane Koszyk Zakresy", + "validRanges": { + "begin": "Zaczynać", + "end": "Koniec" + }, + "matchingLocales": "Dopasowane Locales", + "validLocales": { + "origination": "Od", + "destination": "Do", + "deliveryBegin": "Dostawa Est.", + "deliveryEnd": "Dostawa Est." + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-type-shipping/src/i18n/pt.json b/packages/api-plugin-fulfillment-type-shipping/src/i18n/pt.json new file mode 100644 index 00000000000..0f6a8b41744 --- /dev/null +++ b/packages/api-plugin-fulfillment-type-shipping/src/i18n/pt.json @@ -0,0 +1,81 @@ +[{ + "i18n": "pt", + "ns": "reaction-shipping", + "translation": { + "reaction-shipping": { + "admin": { + "dashboard": { + "shippingLabel": "Remessa", + "shippingTitle": "Remessa", + "shippingDescription": "Fornecer taxas de envio" + }, + "settings": { + "shippingLabel": "Remessa" + } + }, + "checkoutShipping": { + "selectShippingOption": "Selecione a opção de envio", + "noShippingMethods": "Não há métodos de envio estão configurados.", + "contactAdmin": "Entre em contato com o administrador da loja.", + "configureNow": "Configurar agora.", + "shipping": "Remessa" + }, + "shipping": { + "addShippingProvider": "Adicionar provedor de transporte", + "editShippingProvider": "Editar provedor de transporte", + "addShippingMethod": "Adicionar método de envio", + "editShippingMethod": "Editar método de envio", + "deleteShippingMethod": "Excluir método de envio", + "noSettingsForThisView": "Não há definições para este ponto de vista", + "noShippingMethods": "Não há métodos de envio estão configurados.", + "removeShippingMethodConfirm": "Tem certeza de que deseja excluir {{method}}?", + "removeShippingMethodTitle": "Remover Método de Envio", + "shippingMethodDeleted": "Este método de envio foi excluída.", + "removeShippingProvider": "Remover fornecedor de Frete", + "removeShippingProviderConfirm": "Tem certeza de que deseja excluir {{provider}}?", + "shippingProviderSaved": "provedor de envio salvo.", + "shippingProviderUpdated": "dados do provedor envio atualizado.", + "shippingMethodRateAdded": "Envio da tarifa método acrescentou.", + "shippingMethodRateUpdated": "taxa de método de envio atualizado.", + "name": "Nome", + "label": "Etiqueta", + "group": "Grupo", + "cost": "Custo", + "handling": "Manipulação", + "rate": "Taxa", + "enabled": "Ativado", + "disabled": "Desativado", + "addRate": "Adicionar taxa de", + "updateRate": "Taxa de Atualização {{name}}", + "addNewCondition": "Adicionar nova condição", + "deleteCondition": "excluir condição", + "provider": { + "name": "Código de serviço", + "label": "Etiqueta pública", + "enabled": "Ativado" + } + }, + "shippingMethod": { + "name": "Nome do método", + "label": "Etiqueta pública", + "group": "Grupo", + "cost": "Custo", + "handling": "Manipulação", + "rate": "Taxa", + "enabled": "Ativado", + "matchingCartRanges": "Combinando Carrinho Ranges", + "validRanges": { + "begin": "Início", + "end": "Fim" + }, + "matchingLocales": "Localidades combinando", + "validLocales": { + "origination": "A partir da", + "destination": "Para", + "deliveryBegin": "O envio Est.", + "deliveryEnd": "Entrega Est." + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-type-shipping/src/i18n/ro.json b/packages/api-plugin-fulfillment-type-shipping/src/i18n/ro.json new file mode 100644 index 00000000000..0624d40472f --- /dev/null +++ b/packages/api-plugin-fulfillment-type-shipping/src/i18n/ro.json @@ -0,0 +1,81 @@ +[{ + "i18n": "ro", + "ns": "reaction-shipping", + "translation": { + "reaction-shipping": { + "admin": { + "dashboard": { + "shippingLabel": "livrare", + "shippingTitle": "livrare", + "shippingDescription": "Oferi tarife de transport maritim" + }, + "settings": { + "shippingLabel": "livrare" + } + }, + "checkoutShipping": { + "selectShippingOption": "Selectați opțiunea de transport maritim", + "noShippingMethods": "Nu există metode de expediere sunt configurate.", + "contactAdmin": "Contactați administratorul magazinului.", + "configureNow": "Configurati.", + "shipping": "livrare" + }, + "shipping": { + "addShippingProvider": "Adăugați un furnizor de transport maritim", + "editShippingProvider": "Editare furnizori de transport maritim", + "addShippingMethod": "Adăugați o metodă de transport maritim", + "editShippingMethod": "Edita metodă de expediere", + "deleteShippingMethod": "Șterge metodă de expediere", + "noSettingsForThisView": "Nu există setări pentru această vizualizare", + "noShippingMethods": "Nu există metode de expediere sunt configurate.", + "removeShippingMethodConfirm": "Sunteți sigur că doriți să ștergeți {{method}}?", + "removeShippingMethodTitle": "Eliminați Metodă de expediere", + "shippingMethodDeleted": "Această metodă de transport maritim a fost șters.", + "removeShippingProvider": "Furnizorul de transport elimina", + "removeShippingProviderConfirm": "Sunteți sigur că doriți să ștergeți {{provider}}?", + "shippingProviderSaved": "Furnizor de transport maritim salvat.", + "shippingProviderUpdated": "Date de furnizor de transport maritim actualizat.", + "shippingMethodRateAdded": "Rata metodă de expediere adăugată.", + "shippingMethodRateUpdated": "Rata metodă de expediere actualizată.", + "name": "Nume", + "label": "Eticheta", + "group": "grup", + "cost": "A costat", + "handling": "manipularea", + "rate": "Rată", + "enabled": "Activat", + "disabled": "Pentru persoane cu handicap", + "addRate": "adauga o rată", + "updateRate": "Rata de actualizare {{name}}", + "addNewCondition": "Adăugați o condiție nouă", + "deleteCondition": "şterge condiție", + "provider": { + "name": "Codul de service", + "label": "Etichetă publică", + "enabled": "Activat" + } + }, + "shippingMethod": { + "name": "Nume metoda", + "label": "Etichetă publică", + "group": "grup", + "cost": "A costat", + "handling": "manipularea", + "rate": "Rată", + "enabled": "Activat", + "matchingCartRanges": "Potrivire Ranges Coș", + "validRanges": { + "begin": "ÎNCEPE", + "end": "Sfârşit" + }, + "matchingLocales": "potrivire Localizările", + "validLocales": { + "origination": "Din", + "destination": "La", + "deliveryBegin": "De transport maritim Est.", + "deliveryEnd": "Livrare Est." + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-type-shipping/src/i18n/ru.json b/packages/api-plugin-fulfillment-type-shipping/src/i18n/ru.json new file mode 100644 index 00000000000..afdcb285ae7 --- /dev/null +++ b/packages/api-plugin-fulfillment-type-shipping/src/i18n/ru.json @@ -0,0 +1,81 @@ +[{ + "i18n": "ru", + "ns": "reaction-shipping", + "translation": { + "reaction-shipping": { + "admin": { + "dashboard": { + "shippingLabel": "Перевозка", + "shippingTitle": "Перевозка", + "shippingDescription": "Обеспечить стоимость доставки" + }, + "settings": { + "shippingLabel": "Перевозка" + } + }, + "checkoutShipping": { + "selectShippingOption": "Выберите вариант доставки", + "noShippingMethods": "Нет способов доставки не настроены.", + "contactAdmin": "Обратитесь к администратору магазина.", + "configureNow": "Настроить сейчас.", + "shipping": "Перевозка" + }, + "shipping": { + "addShippingProvider": "Добавить поставщика доставки", + "editShippingProvider": "Редактировать поставщик доставка", + "addShippingMethod": "Добавить способ доставки", + "editShippingMethod": "Способ редактирования отправка", + "deleteShippingMethod": "Удалить способ доставки", + "noSettingsForThisView": "Нет настроек для этой точки зрения", + "noShippingMethods": "Нет способов доставки не настроены.", + "removeShippingMethodConfirm": "Вы уверены, что хотите удалить {{method}}?", + "removeShippingMethodTitle": "Удалить Способ доставки", + "shippingMethodDeleted": "Этот способ доставки был удален.", + "removeShippingProvider": "Удалить поставщика доставки", + "removeShippingProviderConfirm": "Вы уверены, что хотите удалить {{provider}}?", + "shippingProviderSaved": "поставщик Доставка сохранен.", + "shippingProviderUpdated": "Поставщик услуг Доставка обновляется.", + "shippingMethodRateAdded": "добавлен скорость Способ доставки.", + "shippingMethodRateUpdated": "Скорость Способ доставки обновлен.", + "name": "Имя", + "label": "Этикетка", + "group": "Группа", + "cost": "Стоимость", + "handling": "обращение", + "rate": "Ставка", + "enabled": "Включено", + "disabled": "Отключено", + "addRate": "Добавить скорость", + "updateRate": "Обновление {{name}} скорости", + "addNewCondition": "Добавить новое условие", + "deleteCondition": "Удалить условие", + "provider": { + "name": "Сервисный код", + "label": "Общественный Этикетка", + "enabled": "Включено" + } + }, + "shippingMethod": { + "name": "Имя метода", + "label": "Общественный Этикетка", + "group": "Группа", + "cost": "Стоимость", + "handling": "обращение", + "rate": "Ставка", + "enabled": "Включено", + "matchingCartRanges": "Matching Корзина Ranges", + "validRanges": { + "begin": "Начать", + "end": "Конец" + }, + "matchingLocales": "Соответствие Локали", + "validLocales": { + "origination": "Из", + "destination": "Для", + "deliveryBegin": "Доставка Est.", + "deliveryEnd": "Доставка Est." + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-type-shipping/src/i18n/sl.json b/packages/api-plugin-fulfillment-type-shipping/src/i18n/sl.json new file mode 100644 index 00000000000..c2b10caf3bb --- /dev/null +++ b/packages/api-plugin-fulfillment-type-shipping/src/i18n/sl.json @@ -0,0 +1,81 @@ +[{ + "i18n": "sl", + "ns": "reaction-shipping", + "translation": { + "reaction-shipping": { + "admin": { + "dashboard": { + "shippingLabel": "Dostava", + "shippingTitle": "Dostava", + "shippingDescription": "Navedite stroške pošiljanja" + }, + "settings": { + "shippingLabel": "Dostava" + } + }, + "checkoutShipping": { + "selectShippingOption": "Izberite možnost ladijskega prometa", + "noShippingMethods": "Ni metode pošiljanja so nastavljeni.", + "contactAdmin": "Obrnite se na skrbnika trgovine.", + "configureNow": "Konfiguracija zdaj.", + "shipping": "Dostava" + }, + "shipping": { + "addShippingProvider": "Dodaj ponudnika ladijskega", + "editShippingProvider": "Uredi ponudnik ladijski promet", + "addShippingMethod": "Dodaj način pošiljanja", + "editShippingMethod": "Uredite način pošiljanja", + "deleteShippingMethod": "Brisanje način pošiljanja", + "noSettingsForThisView": "Ni nastavitev za to mnenje", + "noShippingMethods": "Ni metode pošiljanja so nastavljeni.", + "removeShippingMethodConfirm": "Ali ste prepričani, da želite izbrisati {{method}}?", + "removeShippingMethodTitle": "Odstrani Shipping Method", + "shippingMethodDeleted": "Ta način pošiljanja je bil izbrisan.", + "removeShippingProvider": "Odstrani ponudnika dostave", + "removeShippingProviderConfirm": "Ali ste prepričani, da želite izbrisati {{provider}}?", + "shippingProviderSaved": "Ponudnik prevoz shranjene.", + "shippingProviderUpdated": "Podatki ponudnika prevoz posodabljajo.", + "shippingMethodRateAdded": "Dostava stopnja metoda dodal.", + "shippingMethodRateUpdated": "Dostava stopnja način posodobljena.", + "name": "Name", + "label": "Label", + "group": "skupina", + "cost": "strošek", + "handling": "Ravnanje", + "rate": "Oceniti", + "enabled": "omogočeno", + "disabled": "Onemogočeno", + "addRate": "Dodaj stopnjo", + "updateRate": "Update {{name}} stopnja", + "addNewCondition": "Dodaj nov pogoj", + "deleteCondition": "Brisanje stanje", + "provider": { + "name": "Koda Service", + "label": "javni Label", + "enabled": "omogočeno" + } + }, + "shippingMethod": { + "name": "Ime metoda", + "label": "javni Label", + "group": "skupina", + "cost": "strošek", + "handling": "Ravnanje", + "rate": "Oceniti", + "enabled": "omogočeno", + "matchingCartRanges": "Ujemanje košarice Območja", + "validRanges": { + "begin": "Začeti", + "end": "End" + }, + "matchingLocales": "ujemanje Locales", + "validLocales": { + "origination": "iz", + "destination": "Za", + "deliveryBegin": "Dostava Est.", + "deliveryEnd": "Dostava Est." + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-type-shipping/src/i18n/sv.json b/packages/api-plugin-fulfillment-type-shipping/src/i18n/sv.json new file mode 100644 index 00000000000..193945033f4 --- /dev/null +++ b/packages/api-plugin-fulfillment-type-shipping/src/i18n/sv.json @@ -0,0 +1,81 @@ +[{ + "i18n": "sv", + "ns": "reaction-shipping", + "translation": { + "reaction-shipping": { + "admin": { + "dashboard": { + "shippingLabel": "Frakt", + "shippingTitle": "Frakt", + "shippingDescription": "Tillhandahålla fraktkostnader" + }, + "settings": { + "shippingLabel": "Frakt" + } + }, + "checkoutShipping": { + "selectShippingOption": "Välj leveransalternativ", + "noShippingMethods": "Inga transportsätt konfigureras.", + "contactAdmin": "Kontakta butikshanteraren.", + "configureNow": "Konfigurera nu.", + "shipping": "Frakt" + }, + "shipping": { + "addShippingProvider": "Fogar sändnings provider", + "editShippingProvider": "Redigera leverans provider", + "addShippingMethod": "Lägg fraktsätt", + "editShippingMethod": "Redigera leveranssätt", + "deleteShippingMethod": "Radera fraktsätt", + "noSettingsForThisView": "Inga inställningar för denna uppfattning", + "noShippingMethods": "Inga transportsätt konfigureras.", + "removeShippingMethodConfirm": "Är du säker på att du vill radera {{method}}?", + "removeShippingMethodTitle": "Ta Sändningsmetod", + "shippingMethodDeleted": "Denna leveransmetod har tagits bort.", + "removeShippingProvider": "Avlägsna Shipping Provider", + "removeShippingProviderConfirm": "Är du säker på att du vill radera {{provider}}?", + "shippingProviderSaved": "Speditör sparas.", + "shippingProviderUpdated": "Fraktoperatörsdata uppdateras.", + "shippingMethodRateAdded": "Fraktsätt hastigheten tillsätts.", + "shippingMethodRateUpdated": "Fraktsätt hastighet uppdateras.", + "name": "Namn", + "label": "Etikett", + "group": "Grupp", + "cost": "Kosta", + "handling": "Hantering", + "rate": "Betygsätt", + "enabled": "Aktiverad", + "disabled": "Funktionshindrade", + "addRate": "Lägg hastighet", + "updateRate": "Uppdatering {{name}} hastighet", + "addNewCondition": "Lägg till nyskick", + "deleteCondition": "radera Förhållande", + "provider": { + "name": "service-kod", + "label": "offentliga Label", + "enabled": "Aktiverad" + } + }, + "shippingMethod": { + "name": "metod Namn", + "label": "offentliga Label", + "group": "Grupp", + "cost": "Kosta", + "handling": "Hantering", + "rate": "Betygsätt", + "enabled": "Aktiverad", + "matchingCartRanges": "Matchning Vagn Ranges", + "validRanges": { + "begin": "Börja", + "end": "Slut" + }, + "matchingLocales": "matchning språkversioner", + "validLocales": { + "origination": "Från", + "destination": "Till", + "deliveryBegin": "Shipping Est.", + "deliveryEnd": "Leverans Est." + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-type-shipping/src/i18n/tr.json b/packages/api-plugin-fulfillment-type-shipping/src/i18n/tr.json new file mode 100644 index 00000000000..a45de6f1c14 --- /dev/null +++ b/packages/api-plugin-fulfillment-type-shipping/src/i18n/tr.json @@ -0,0 +1,81 @@ +[{ + "i18n": "tr", + "ns": "reaction-shipping", + "translation": { + "reaction-shipping": { + "admin": { + "dashboard": { + "shippingLabel": "Nakliye", + "shippingTitle": "Nakliye", + "shippingDescription": "nakliye fiyatları sağlamamıza" + }, + "settings": { + "shippingLabel": "Nakliye" + } + }, + "checkoutShipping": { + "selectShippingOption": "Seç nakliye seçeneği", + "noShippingMethods": "Hiçbir nakliye yöntemleri yapılandırılır.", + "contactAdmin": "Lütfen mağaza yöneticisine başvurun.", + "configureNow": "Şimdi yapılandırın.", + "shipping": "Nakliye" + }, + "shipping": { + "addShippingProvider": "nakliye sağlayıcı ekle", + "editShippingProvider": "Düzenleme nakliye sağlayıcı", + "addShippingMethod": "nakliye yöntemi ekleyin", + "editShippingMethod": "Düzenleme gönderim yöntemi", + "deleteShippingMethod": "nakliye yöntemini sil", + "noSettingsForThisView": "Bu görünüm için ayar yok", + "noShippingMethods": "Hiçbir nakliye yöntemleri yapılandırılır.", + "removeShippingMethodConfirm": "Eğer {{method}} silmek istediğinizden emin misiniz?", + "removeShippingMethodTitle": "Nakliye Yöntemi kaldır", + "shippingMethodDeleted": "Bu nakliye yöntemi silindi.", + "removeShippingProvider": "Nakliye Sağlayıcı kaldır", + "removeShippingProviderConfirm": "Eğer {{provider}} silmek istediğinizden emin misiniz?", + "shippingProviderSaved": "Kargo sağlayıcı kaydedildi.", + "shippingProviderUpdated": "Kargo sağlayıcı verileri güncellendi.", + "shippingMethodRateAdded": "Nakliye yöntemi oranı eklendi.", + "shippingMethodRateUpdated": "Nakliye yöntemi oranı güncellendi.", + "name": "Isim", + "label": "Etiket", + "group": "grup", + "cost": "Maliyet", + "handling": "kullanma", + "rate": "Oran", + "enabled": "Etkin", + "disabled": "engelli", + "addRate": "oranı ekle", + "updateRate": "Güncelleme {{name}} oranı", + "addNewCondition": "Yeni koşul ekle", + "deleteCondition": "koşulu sil", + "provider": { + "name": "servis Kodu", + "label": "kamu Etiket", + "enabled": "Etkin" + } + }, + "shippingMethod": { + "name": "yöntem Adı", + "label": "kamu Etiket", + "group": "grup", + "cost": "Maliyet", + "handling": "kullanma", + "rate": "Oran", + "enabled": "Etkin", + "matchingCartRanges": "Sepet aralıkları Eşleştirme", + "validRanges": { + "begin": "Başla", + "end": "Son" + }, + "matchingLocales": "Yerel eşleştirme", + "validLocales": { + "origination": "Itibaren", + "destination": "Için", + "deliveryBegin": "Nakliye Est.", + "deliveryEnd": "Teslim Tahmini." + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-type-shipping/src/i18n/vi.json b/packages/api-plugin-fulfillment-type-shipping/src/i18n/vi.json new file mode 100644 index 00000000000..b91be913735 --- /dev/null +++ b/packages/api-plugin-fulfillment-type-shipping/src/i18n/vi.json @@ -0,0 +1,81 @@ +[{ + "i18n": "vi", + "ns": "reaction-shipping", + "translation": { + "reaction-shipping": { + "admin": { + "dashboard": { + "shippingLabel": "Đang chuyển hàng", + "shippingTitle": "Đang chuyển hàng", + "shippingDescription": "Cung cấp mức giá vận chuyển" + }, + "settings": { + "shippingLabel": "Đang chuyển hàng" + } + }, + "checkoutShipping": { + "selectShippingOption": "Chọn tùy chọn vận chuyển", + "noShippingMethods": "Không có phương pháp vận chuyển được cấu hình.", + "contactAdmin": "Vui lòng liên hệ với quản trị viên kho.", + "configureNow": "Cấu hình hiện nay.", + "shipping": "Đang chuyển hàng" + }, + "shipping": { + "addShippingProvider": "Thêm nhà cung cấp vận chuyển", + "editShippingProvider": "Sửa nhà cung cấp vận chuyển", + "addShippingMethod": "Thêm phương thức vận chuyển", + "editShippingMethod": "Chỉnh sửa phương thức vận chuyển", + "deleteShippingMethod": "Xóa phương thức vận chuyển", + "noSettingsForThisView": "Không có thiết lập cho quan điểm này", + "noShippingMethods": "Không có phương pháp vận chuyển được cấu hình.", + "removeShippingMethodConfirm": "Bạn có chắc chắn muốn xóa {{method}}?", + "removeShippingMethodTitle": "Di Shipping Method", + "shippingMethodDeleted": "Phương pháp vận chuyển này đã bị xóa.", + "removeShippingProvider": "Di Vận Chuyển Nhà cung cấp", + "removeShippingProviderConfirm": "Bạn có chắc chắn muốn xóa {{provider}}?", + "shippingProviderSaved": "cung cấp dịch vụ vận chuyển lưu.", + "shippingProviderUpdated": "cung cấp dữ liệu vận chuyển được cập nhật.", + "shippingMethodRateAdded": "tỷ lệ phương pháp vận chuyển thêm.", + "shippingMethodRateUpdated": "phương pháp tỷ lệ vận chuyển được cập nhật.", + "name": "Tên", + "label": "Nhãn", + "group": "Nhóm", + "cost": "Giá cả", + "handling": "xử lý", + "rate": "Tỷ lệ", + "enabled": "Bật", + "disabled": "Tàn tật", + "addRate": "Thêm tỷ lệ", + "updateRate": "tốc độ cập nhật {{name}}", + "addNewCondition": "Thêm điều kiện mới", + "deleteCondition": "xóa điều kiện", + "provider": { + "name": "Mã dịch vụ", + "label": "Nhãn Công", + "enabled": "Bật" + } + }, + "shippingMethod": { + "name": "Tên phương pháp", + "label": "Nhãn Công", + "group": "Nhóm", + "cost": "Giá cả", + "handling": "xử lý", + "rate": "Tỷ lệ", + "enabled": "Bật", + "matchingCartRanges": "Phù hợp với Ranges Giỏ hàng", + "validRanges": { + "begin": "bắt đầu", + "end": "Kết thúc" + }, + "matchingLocales": "phù hợp với miền địa phương", + "validLocales": { + "origination": "Từ", + "destination": "Đến", + "deliveryBegin": "Vận Chuyển Est.", + "deliveryEnd": "Giao hàng Est." + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-type-shipping/src/i18n/zh.json b/packages/api-plugin-fulfillment-type-shipping/src/i18n/zh.json new file mode 100644 index 00000000000..5d72cb0eab3 --- /dev/null +++ b/packages/api-plugin-fulfillment-type-shipping/src/i18n/zh.json @@ -0,0 +1,81 @@ +[{ + "i18n": "zh", + "ns": "reaction-shipping", + "translation": { + "reaction-shipping": { + "admin": { + "dashboard": { + "shippingLabel": "运输", + "shippingTitle": "运输", + "shippingDescription": "提供运费" + }, + "settings": { + "shippingLabel": "运输" + } + }, + "checkoutShipping": { + "selectShippingOption": "选择送货方式", + "noShippingMethods": "没有送货方式配置。", + "contactAdmin": "请联系店主管理员。", + "configureNow": "现在配置。", + "shipping": "运输" + }, + "shipping": { + "addShippingProvider": "添加航运商", + "editShippingProvider": "编辑航运商", + "addShippingMethod": "添加送货方式", + "editShippingMethod": "编辑送货方式", + "deleteShippingMethod": "删除运输方式", + "noSettingsForThisView": "对于这种观点没有设置", + "noShippingMethods": "没有送货方式配置。", + "removeShippingMethodConfirm": "你确定要删除{{method}}?", + "removeShippingMethodTitle": "除去运输方式", + "shippingMethodDeleted": "这种运输方式已被删除。", + "removeShippingProvider": "除去运输提供商", + "removeShippingProviderConfirm": "你确定要删除{{provider}}?", + "shippingProviderSaved": "航运提供商保存。", + "shippingProviderUpdated": "航运商的数据更新。", + "shippingMethodRateAdded": "送货方式加入速度。", + "shippingMethodRateUpdated": "更新送货方法率。", + "name": "名字", + "label": "标签", + "group": "组", + "cost": "费用", + "handling": "处理", + "rate": "率", + "enabled": "启用", + "disabled": "残疾人", + "addRate": "添加率", + "updateRate": "更新{{name}}率", + "addNewCondition": "新增条件", + "deleteCondition": "删除条件", + "provider": { + "name": "服务代码", + "label": "公共标签", + "enabled": "启用" + } + }, + "shippingMethod": { + "name": "方法名称", + "label": "公共标签", + "group": "组", + "cost": "费用", + "handling": "处理", + "rate": "率", + "enabled": "启用", + "matchingCartRanges": "匹配车范围", + "validRanges": { + "begin": "开始", + "end": "结束" + }, + "matchingLocales": "匹配的语言环境", + "validLocales": { + "origination": "从", + "destination": "至", + "deliveryBegin": "航运预估。", + "deliveryEnd": "交货预估。" + } + } + } + } +}] diff --git a/packages/api-plugin-fulfillment-type-shipping/src/index.js b/packages/api-plugin-fulfillment-type-shipping/src/index.js index 04ede1902a1..976aa496480 100644 --- a/packages/api-plugin-fulfillment-type-shipping/src/index.js +++ b/packages/api-plugin-fulfillment-type-shipping/src/index.js @@ -1,4 +1,5 @@ import { createRequire } from "module"; +import i18n from "./i18n/index.js"; import schemas from "./schemas/index.js"; import startup from "./startup.js"; @@ -22,6 +23,7 @@ export default async function register(app) { label: "Fulfillment Type Shipping", name: "fulfillment-type-shipping", version: pkg.version, + i18n, graphQL: { schemas }, diff --git a/packages/api-plugin-fulfillment/src/i18n/en.json b/packages/api-plugin-fulfillment/src/i18n/en.json new file mode 100644 index 00000000000..e674bcc7453 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/i18n/en.json @@ -0,0 +1,37 @@ +[{ + "i18n": "en", + "ns": "reaction-fulfillment", + "translation": { + "reaction-fulfillment": { + "admin": { + "dashboard": { + "fulfillmentLabel": "Fulfillment", + "fulfillmentTitle": "Fulfillment", + "fulfillmentDescription": "Manage Fulfillment" + }, + "settings": { + "fulfillmentLabel": "Fulfillment", + "editFulfillmentType": "Edit Fulfillment Type", + "editFulfillmentMethod": "Edit Fulfillment Method" + } + }, + "checkoutShipping": { + "selectFulfillmentType": "Select fulfillment type for the item", + "noFulfillmentTypes": "No fulfillment types are configured.", + "noFulfillmentMethods": "No fulfillment methods are configured.", + "addiionalDetails": "Additional details required for the selected fulfillment method", + "contactAdmin": "Please contact the store administrator.", + "configureNow": "Configure now.", + "fulfillment": "Fulfillment" + }, + "fulfillment": { + "editFulfillmentProvider": "Edit fulfillment provider", + "editFulfillmentMethod": "Edit fulfillment method", + "noSettingsForThisView": "No settings for this view", + "noFulfillmentMethods": "No fulfillment methods are configured.", + "fulfillmentProviderUpdated": "Fulfillment provider data updated.", + "fulfillmentMethodUpdated": "Fulfillment method updated." + } + } + } +}] diff --git a/packages/api-plugin-fulfillment/src/i18n/index.js b/packages/api-plugin-fulfillment/src/i18n/index.js new file mode 100644 index 00000000000..34f686a3c25 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/i18n/index.js @@ -0,0 +1,12 @@ +import en from "./en.json"; + +// +// we want all the files in individual +// imports for easier handling by +// automated translation software +// +export default { + translations: [ + ...en + ] +}; diff --git a/packages/api-plugin-fulfillment/src/index.js b/packages/api-plugin-fulfillment/src/index.js index 64f22e78cc9..9f2c44e8df1 100644 --- a/packages/api-plugin-fulfillment/src/index.js +++ b/packages/api-plugin-fulfillment/src/index.js @@ -1,4 +1,5 @@ import { createRequire } from "module"; +import i18n from "./i18n/index.js"; import mutations from "./mutations/index.js"; import queries from "./queries/index.js"; import resolvers from "./resolvers/index.js"; @@ -21,6 +22,7 @@ export default async function register(app) { label: "Fulfillment", name: "fulfillment", version: pkg.version, + i18n, collections: { FulfillmentRestrictions: { name: "FulfillmentRestrictions",