From c94c05cdc98a430a4cd36575111100609c666730 Mon Sep 17 00:00:00 2001 From: Sujith Date: Wed, 6 Jul 2022 22:52:52 +0530 Subject: [PATCH 1/5] MCOSS-52 -FF grouping, get FF type user input Signed-off-by: Sujith --- src/index.js | 5 +- src/schemas/cart.graphql | 6 +++ src/simpleSchemas.js | 19 +++++--- src/startup.js | 37 +++++++++++++++ src/util/addCartItems.js | 8 +++- src/util/updateCartFulfillmentGroups.js | 63 ++++++++++++++++++------- src/xforms/xformCartCheckout.js | 2 +- 7 files changed, 113 insertions(+), 27 deletions(-) diff --git a/src/index.js b/src/index.js index ad20648..3aad3da 100644 --- a/src/index.js +++ b/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 } from "./simpleSchemas.js"; import startup from "./startup.js"; /** @@ -59,7 +59,8 @@ export default async function register(app) { policies, simpleSchemas: { Cart, - CartItem + CartItem, + Shipment } }); } diff --git a/src/schemas/cart.graphql b/src/schemas/cart.graphql index 284cb89..dc9f57c 100644 --- a/src/schemas/cart.graphql +++ b/src/schemas/cart.graphql @@ -230,6 +230,9 @@ 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 date and time at which this item was last updated" updatedAt: DateTime! @@ -314,6 +317,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" diff --git a/src/simpleSchemas.js b/src/simpleSchemas.js index 329b05b..b7fc234 100644 --- a/src/simpleSchemas.js +++ b/src/simpleSchemas.js @@ -532,7 +532,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" @@ -570,11 +570,12 @@ const Shipment = new SimpleSchema({ type: String, optional: true }, - "type": { - type: String, - allowedValues: ["shipping"], - defaultValue: "shipping" - }, + // type extended in startup with dynamic allowedValues + // "type": { + // type: String, + // allowedValues: ["shipping"], + // defaultValue: "shipping" + // }, "parcel": { type: ShippingParcel, optional: true @@ -661,6 +662,7 @@ 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} variantId required * @property {String} variantTitle Title from the selected variant */ @@ -733,6 +735,11 @@ export const CartItem = new SimpleSchema({ blackbox: true }, "updatedAt": Date, + "selectedFulfillmentType": { + type: String, + defaultValue: "", + optional: true + }, "variantId": { type: String, optional: true diff --git a/src/startup.js b/src/startup.js index 404c90f..f5b02a1 100644 --- a/src/startup.js +++ b/src/startup.js @@ -88,6 +88,41 @@ async function updateAllCartsForVariant({ Cart, context, variant }) { return null; } +/** + * @summary Extend the schema with updated allowedValues + * @param {Object} context Startup context + * @returns {undefined} + */ + async function extendSchemas(context) { + const allFulfillmentTypesArray = await context.queries.allFulfillmentTypes(context); + + if (!allFulfillmentTypesArray || allFulfillmentTypesArray.length === 0){ + Logger.warn("No fulfillment types available, setting 'shipping' as default"); + allFulfillmentTypesArray = ['shipping']; + } + + const { simpleSchemas: { Shipment, CartItem } } = context; + const schemaShipmentExtension = { + type: { + type: String, + allowedValues: allFulfillmentTypesArray, + defaultValue: allFulfillmentTypesArray[0] + } + } + Shipment.extend(schemaShipmentExtension); + + const schemaCartItemExtension = { + "supportedFulfillmentTypes": { + type: Array + }, + "supportedFulfillmentTypes.$": { + type: String, + allowedValues: allFulfillmentTypesArray, + }, + } + CartItem.extend(schemaCartItemExtension); +} + /** * @summary Called on startup * @param {Object} context Startup context @@ -98,6 +133,8 @@ export default async function cartStartup(context) { const { appEvents, collections } = context; const { Cart } = collections; + await extendSchemas(context); + // When an order is created, delete the source cart appEvents.on("afterOrderCreate", async ({ order }) => { const { cartId } = order; diff --git a/src/util/addCartItems.js b/src/util/addCartItems.js index 552f266..99785e7 100644 --- a/src/util/addCartItems.js +++ b/src/util/addCartItems.js @@ -21,6 +21,10 @@ const inputItemSchema = new SimpleSchema({ "price.amount": { type: Number, optional: true + }, + "selectedFulfillmentType": { + type: String, + optional: true } }); @@ -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/src/util/updateCartFulfillmentGroups.js b/src/util/updateCartFulfillmentGroups.js index 7bc45b3..7bceb1f 100644 --- a/src/util/updateCartFulfillmentGroups.js +++ b/src/util/updateCartFulfillmentGroups.js @@ -13,6 +13,32 @@ function determineInitialGroupForItem(currentGroups, supportedFulfillmentTypes, 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 {String} shopId The ID of the shop that owns the item (product) + * @returns {undefined} + */ + function checkAndAddToGroup(currentGroups, fulfillmentType, item) { + const group = determineInitialGroupForItem(currentGroups, [fulfillmentType], item.shopId); + 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: fulfillmentType, + }); + } 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); + } +} + /** * @summary Updates the `shipping` property on a `cart` * @param {Object} context App context @@ -23,14 +49,26 @@ export default 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. + // Update: Refer MCOSS-52: + // 1. If the selectedFulfillmentType is not provided for an item, then + // that item should be present in all groups corresponding to it's supportedFulfillmentTypes + // If selectedFulfillmentType is provided, we keep the item only in that group. + const currentGroups = cart.shipping || []; (cart.items || []).forEach((item) => { let { supportedFulfillmentTypes } = item; + + // 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; + // 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; + // Commenting out the below check since the item should below to all supported groups, + // and not just one if the selectedFulfillmentType is not provided. + // const itemAlreadyInTheGroup = currentGroups.find(({ itemIds }) => itemIds && itemIds.includes(item._id)); + // if (itemAlreadyInTheGroup) return; if (!supportedFulfillmentTypes || supportedFulfillmentTypes.length === 0) { supportedFulfillmentTypes = ["shipping"]; @@ -38,22 +76,13 @@ export default function updateCartFulfillmentGroups(context, cart) { // 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 (!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] + // If selectedFulfillmentType is provided, add the item only to that group, else add item to all supported groups + if (selectedFulfillmentType) { + checkAndAddToGroup(currentGroups, selectedFulfillmentType, item); + } else { + supportedFulfillmentTypes.forEach(ffType => { + checkAndAddToGroup(currentGroups, ffType, item); }); - } 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); } }); diff --git a/src/xforms/xformCartCheckout.js b/src/xforms/xformCartCheckout.js index 707f41a..ec0dcc5 100644 --- a/src/xforms/xformCartCheckout.js +++ b/src/xforms/xformCartCheckout.js @@ -65,7 +65,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 9607c897c4c4853d4365056d9242eaa0b320dbc4 Mon Sep 17 00:00:00 2001 From: Sujith Date: Thu, 7 Jul 2022 11:26:28 +0530 Subject: [PATCH 2/5] Linter fix Signed-off-by: Sujith --- src/startup.js | 12 ++++++------ src/util/updateCartFulfillmentGroups.js | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/startup.js b/src/startup.js index f5b02a1..3dfab54 100644 --- a/src/startup.js +++ b/src/startup.js @@ -93,12 +93,12 @@ async function updateAllCartsForVariant({ Cart, context, variant }) { * @param {Object} context Startup context * @returns {undefined} */ - async function extendSchemas(context) { - const allFulfillmentTypesArray = await context.queries.allFulfillmentTypes(context); +async function extendSchemas(context) { + let allFulfillmentTypesArray = await context.queries.allFulfillmentTypes(context); if (!allFulfillmentTypesArray || allFulfillmentTypesArray.length === 0){ Logger.warn("No fulfillment types available, setting 'shipping' as default"); - allFulfillmentTypesArray = ['shipping']; + allFulfillmentTypesArray = ["shipping"]; } const { simpleSchemas: { Shipment, CartItem } } = context; @@ -117,9 +117,9 @@ async function updateAllCartsForVariant({ Cart, context, variant }) { }, "supportedFulfillmentTypes.$": { type: String, - allowedValues: allFulfillmentTypesArray, - }, - } + allowedValues: allFulfillmentTypesArray + } + }; CartItem.extend(schemaCartItemExtension); } diff --git a/src/util/updateCartFulfillmentGroups.js b/src/util/updateCartFulfillmentGroups.js index 7bceb1f..2acbeb8 100644 --- a/src/util/updateCartFulfillmentGroups.js +++ b/src/util/updateCartFulfillmentGroups.js @@ -28,7 +28,7 @@ function determineInitialGroupForItem(currentGroups, supportedFulfillmentTypes, _id: Random.id(), itemIds: [item._id], shopId: item.shopId, - type: fulfillmentType, + type: fulfillmentType }); } else if (!group.itemIds) { // If there is a compatible group but it has no items array, add one with just this item in it From 2799f331882b777e31e0d135c4c52148af4ef144 Mon Sep 17 00:00:00 2001 From: Sujith Date: Sun, 10 Jul 2022 22:41:09 +0530 Subject: [PATCH 3/5] Linter fix2 Signed-off-by: Sujith --- src/startup.js | 4 ++-- src/util/updateCartFulfillmentGroups.js | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/startup.js b/src/startup.js index 3dfab54..36c6020 100644 --- a/src/startup.js +++ b/src/startup.js @@ -96,7 +96,7 @@ async function updateAllCartsForVariant({ Cart, context, variant }) { async function extendSchemas(context) { let allFulfillmentTypesArray = await context.queries.allFulfillmentTypes(context); - if (!allFulfillmentTypesArray || allFulfillmentTypesArray.length === 0){ + if (!allFulfillmentTypesArray || allFulfillmentTypesArray.length === 0) { Logger.warn("No fulfillment types available, setting 'shipping' as default"); allFulfillmentTypesArray = ["shipping"]; } @@ -108,7 +108,7 @@ async function extendSchemas(context) { allowedValues: allFulfillmentTypesArray, defaultValue: allFulfillmentTypesArray[0] } - } + }; Shipment.extend(schemaShipmentExtension); const schemaCartItemExtension = { diff --git a/src/util/updateCartFulfillmentGroups.js b/src/util/updateCartFulfillmentGroups.js index 2acbeb8..22b617e 100644 --- a/src/util/updateCartFulfillmentGroups.js +++ b/src/util/updateCartFulfillmentGroups.js @@ -17,10 +17,10 @@ function determineInitialGroupForItem(currentGroups, supportedFulfillmentTypes, * @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 {String} shopId The ID of the shop that owns the item (product) + * @param {Object} item The item (product) object * @returns {undefined} */ - function checkAndAddToGroup(currentGroups, fulfillmentType, item) { +function checkAndAddToGroup(currentGroups, fulfillmentType, item) { const group = determineInitialGroupForItem(currentGroups, [fulfillmentType], item.shopId); if (!group) { // If no compatible group, add one with initially just this item in it @@ -49,8 +49,8 @@ export default 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. - // Update: Refer MCOSS-52: - // 1. If the selectedFulfillmentType is not provided for an item, then + // Update: Refer MCOSS-52: + // 1. If the selectedFulfillmentType is not provided for an item, then // that item should be present in all groups corresponding to it's supportedFulfillmentTypes // If selectedFulfillmentType is provided, we keep the item only in that group. @@ -59,9 +59,9 @@ export default function updateCartFulfillmentGroups(context, cart) { (cart.items || []).forEach((item) => { let { supportedFulfillmentTypes } = item; - // This is a new optional field that UI can pass in case the user selects fulfillment type + // 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; + const { selectedFulfillmentType } = 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 @@ -80,7 +80,7 @@ export default function updateCartFulfillmentGroups(context, cart) { if (selectedFulfillmentType) { checkAndAddToGroup(currentGroups, selectedFulfillmentType, item); } else { - supportedFulfillmentTypes.forEach(ffType => { + supportedFulfillmentTypes.forEach((ffType) => { checkAndAddToGroup(currentGroups, ffType, item); }); } From c28ccb3fa9c1977e5ea8ec1d595c24d2d704079b Mon Sep 17 00:00:00 2001 From: Sujith Date: Mon, 11 Jul 2022 08:25:05 +0530 Subject: [PATCH 4/5] Node version to 14.18.1 Signed-off-by: Sujith --- .circleci/config.yml | 8 ++++---- .nvmrc | 2 +- package.json | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 426ec79..ea38856 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,7 +2,7 @@ version: 2 jobs: build: docker: - - image: node:12.14.1 + - image: node:14.18.1 dependencies: pre: @@ -20,7 +20,7 @@ jobs: deploy: docker: - - image: node:12.14.1 + - image: node:14.18.1 steps: - checkout @@ -35,7 +35,7 @@ jobs: lint: docker: - - image: node:12.14.1 + - image: node:14.18.1 steps: - checkout @@ -50,7 +50,7 @@ jobs: test: docker: - - image: node:12.14.1 + - image: node:14.18.1 steps: - checkout diff --git a/.nvmrc b/.nvmrc index 5c088dd..31102b2 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -12.14.1 +14.18.1 diff --git a/package.json b/package.json index 6349070..ae2ae55 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "index.js", "type": "module", "engines": { - "node": ">=12.14.1" + "node": ">=14.18.1" }, "homepage": "https://github.com/reactioncommerce/api-plugin-carts", "url": "https://github.com/reactioncommerce/api-plugin-carts", From ce4ea4eb046a2a8fe2bb63c89b7101a657e4669c Mon Sep 17 00:00:00 2001 From: Sujith Date: Tue, 2 Aug 2022 20:32:28 +0530 Subject: [PATCH 5/5] feat: fulfillment changes phase-1-A Signed-off-by: Sujith --- src/simpleSchemas.js | 6 ++++++ src/startup.js | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/simpleSchemas.js b/src/simpleSchemas.js index b7fc234..f4f96b5 100644 --- a/src/simpleSchemas.js +++ b/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 common identifier * @property {String} label Public label * @property {String} group Group, allowed values: `Ground`, `Priority`, `One Day`, `Free` * @property {Number} cost optional @@ -225,6 +226,11 @@ const ShippingMethod = new SimpleSchema({ type: String, label: "Public Label" }, + "fulfillmentMethod": { + type: String, + optional: true, + label: "Method name Common identifier" + }, "group": { type: String, label: "Group", diff --git a/src/startup.js b/src/startup.js index 36c6020..5d3695c 100644 --- a/src/startup.js +++ b/src/startup.js @@ -94,7 +94,7 @@ async function updateAllCartsForVariant({ Cart, context, variant }) { * @returns {undefined} */ async function extendSchemas(context) { - let allFulfillmentTypesArray = await context.queries.allFulfillmentTypes(context); + let allFulfillmentTypesArray = context.allRegisteredFulfillmentTypes?.registeredFulfillmentTypes; if (!allFulfillmentTypesArray || allFulfillmentTypesArray.length === 0) { Logger.warn("No fulfillment types available, setting 'shipping' as default");