fix(core-flows): shipping options for cart (#9343)

FIXES: CC-536

Co-authored-by: Frane Polić <16856471+fPolic@users.noreply.github.com>
This commit is contained in:
Carlos R. L. Rodrigues
2024-09-27 09:32:32 -03:00
committed by GitHub
parent 74b3385a65
commit 19bc8d7f61
8 changed files with 136 additions and 106 deletions
@@ -200,7 +200,7 @@ medusaIntegrationTestRunner({
thumbnail: "test-image.png",
status: "draft",
description: "test-product-description\ntest line 2",
options: [
options: expect.arrayContaining([
expect.objectContaining({
title: "size",
values: expect.arrayContaining([
@@ -220,7 +220,7 @@ medusaIntegrationTestRunner({
}),
]),
}),
],
]),
images: expect.arrayContaining([
expect.objectContaining({
url: "test-image.png",
@@ -243,12 +243,12 @@ medusaIntegrationTestRunner({
collection: expect.objectContaining({
id: baseCollection.id,
}),
variants: [
variants: expect.arrayContaining([
expect.objectContaining({
title: "Test variant",
allow_backorder: false,
manage_inventory: true,
prices: [
prices: expect.arrayContaining([
expect.objectContaining({
currency_code: "dkk",
amount: 30,
@@ -261,21 +261,21 @@ medusaIntegrationTestRunner({
currency_code: "usd",
amount: 100,
}),
],
options: [
]),
options: expect.arrayContaining([
expect.objectContaining({
value: "large",
}),
expect.objectContaining({
value: "green",
}),
],
]),
}),
expect.objectContaining({
title: "Test variant 2",
allow_backorder: false,
manage_inventory: true,
prices: [
prices: expect.arrayContaining([
expect.objectContaining({
currency_code: "dkk",
amount: 50,
@@ -288,17 +288,17 @@ medusaIntegrationTestRunner({
currency_code: "usd",
amount: 200,
}),
],
options: [
]),
options: expect.arrayContaining([
expect.objectContaining({
value: "small",
}),
expect.objectContaining({
value: "green",
}),
],
]),
}),
],
]),
created_at: expect.any(String),
updated_at: expect.any(String),
}),
@@ -309,7 +309,7 @@ medusaIntegrationTestRunner({
thumbnail: "test-image.png",
status: "proposed",
description: "test-product-description",
options: [
options: expect.arrayContaining([
expect.objectContaining({
title: "size",
values: expect.arrayContaining([
@@ -326,7 +326,7 @@ medusaIntegrationTestRunner({
}),
]),
}),
],
]),
images: expect.arrayContaining([
expect.objectContaining({
url: "test-image.png",
@@ -344,12 +344,12 @@ medusaIntegrationTestRunner({
id: baseType.id,
}),
collection: null,
variants: [
variants: expect.arrayContaining([
expect.objectContaining({
title: "Test variant",
allow_backorder: false,
manage_inventory: true,
prices: [
prices: expect.arrayContaining([
expect.objectContaining({
currency_code: "dkk",
amount: 30,
@@ -362,17 +362,17 @@ medusaIntegrationTestRunner({
currency_code: "usd",
amount: 100,
}),
],
options: [
]),
options: expect.arrayContaining([
expect.objectContaining({
value: "large",
}),
expect.objectContaining({
value: "green",
}),
],
]),
}),
],
]),
created_at: expect.any(String),
updated_at: expect.any(String),
}),
@@ -613,7 +613,7 @@ medusaIntegrationTestRunner({
thumbnail: "test-image.png",
status: "draft",
description: "test-product-description",
options: [
options: expect.arrayContaining([
expect.objectContaining({
title: "Size",
values: expect.arrayContaining([
@@ -628,7 +628,7 @@ medusaIntegrationTestRunner({
}),
]),
}),
],
]),
images: expect.arrayContaining([
expect.objectContaining({
url: "test-image.png",
@@ -645,7 +645,7 @@ medusaIntegrationTestRunner({
collection: expect.objectContaining({
id: baseCollection.id,
}),
variants: [
variants: expect.arrayContaining([
expect.objectContaining({
title: "Test variant",
sku: "test-sku-2",
@@ -689,7 +689,7 @@ medusaIntegrationTestRunner({
barcode: "test-barcode-4",
allow_backorder: false,
manage_inventory: true,
prices: [
prices: expect.arrayContaining([
expect.objectContaining({
currency_code: "usd",
amount: 100,
@@ -703,14 +703,14 @@ medusaIntegrationTestRunner({
currency_code: "dkk",
amount: 30,
}),
],
]),
options: [
expect.objectContaining({
value: "Large",
}),
],
}),
],
]),
created_at: expect.any(String),
updated_at: expect.any(String),
}),
@@ -723,7 +723,7 @@ medusaIntegrationTestRunner({
status: "draft",
description:
"Hopper Stripes Bedding, available as duvet cover, pillow sham and sheet.\\n100% organic cotton, soft and crisp to the touch. Made in Portugal.",
options: [
options: expect.arrayContaining([
expect.objectContaining({
title: "test-option-1",
values: expect.arrayContaining([
@@ -740,7 +740,7 @@ medusaIntegrationTestRunner({
}),
]),
}),
],
]),
images: expect.arrayContaining([
expect.objectContaining({
url: "test-image.png",
@@ -748,14 +748,14 @@ medusaIntegrationTestRunner({
]),
tags: [],
type: null,
variants: [
variants: expect.arrayContaining([
expect.objectContaining({
title: "Test variant",
sku: "test-sku-1-1",
barcode: "test-barcode-1-1",
allow_backorder: false,
manage_inventory: true,
prices: [
prices: expect.arrayContaining([
expect.objectContaining({
currency_code: "usd",
rules: {
@@ -767,17 +767,17 @@ medusaIntegrationTestRunner({
currency_code: "usd",
amount: 1.1,
}),
],
options: [
]),
options: expect.arrayContaining([
expect.objectContaining({
value: "option 1 value red",
}),
expect.objectContaining({
value: "option 2 value 1",
}),
],
]),
}),
],
]),
created_at: expect.any(String),
updated_at: expect.any(String),
}),
@@ -200,6 +200,7 @@ medusaIntegrationTestRunner({
)
const shippingOptions = resp.data.shipping_options
expect(shippingOptions).toHaveLength(1)
expect(shippingOptions[0]).toEqual(
expect.objectContaining({
@@ -1,3 +1,4 @@
import { MedusaError } from "@medusajs/framework/utils"
import {
WorkflowData,
createWorkflow,
@@ -74,6 +75,13 @@ export const addShippingMethodToWorkflow = createWorkflow(
(so) => so.id === option.id
)!
if (!shippingOption?.calculated_price) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Shipping option with ID ${shippingOption.id} do not have a price`
)
}
return {
shipping_option_id: shippingOption.id,
amount: shippingOption.calculated_price.calculated_amount,
@@ -18,87 +18,101 @@ export const listShippingOptionsForCartWorkflow = createWorkflow(
(input: WorkflowData<ListShippingOptionsForCartWorkflowInputDTO>) => {
const scLocationFulfillmentSets = useRemoteQueryStep({
entry_point: "sales_channels",
fields: [
"stock_locations.fulfillment_sets.id",
"stock_locations.fulfillment_sets.name",
"stock_locations.fulfillment_sets.price_type",
"stock_locations.fulfillment_sets.service_zone_id",
"stock_locations.fulfillment_sets.shipping_profile_id",
"stock_locations.fulfillment_sets.provider_id",
"stock_locations.fulfillment_sets.data",
"stock_locations.fulfillment_sets.amount",
"stock_locations.fulfillment_sets.service_zones.shipping_options.id",
"stock_locations.fulfillment_sets.service_zones.shipping_options.name",
"stock_locations.fulfillment_sets.service_zones.shipping_options.price_type",
"stock_locations.fulfillment_sets.service_zones.shipping_options.service_zone_id",
"stock_locations.fulfillment_sets.service_zones.shipping_options.shipping_profile_id",
"stock_locations.fulfillment_sets.service_zones.shipping_options.provider_id",
"stock_locations.fulfillment_sets.service_zones.shipping_options.data",
"stock_locations.fulfillment_sets.service_zones.shipping_options.amount",
"stock_locations.fulfillment_sets.service_zones.shipping_options.type.id",
"stock_locations.fulfillment_sets.service_zones.shipping_options.type.label",
"stock_locations.fulfillment_sets.service_zones.shipping_options.type.description",
"stock_locations.fulfillment_sets.service_zones.shipping_options.type.code",
"stock_locations.fulfillment_sets.service_zones.shipping_options.provider.id",
"stock_locations.fulfillment_sets.service_zones.shipping_options.provider.is_enabled",
"stock_locations.fulfillment_sets.service_zones.shipping_options.calculated_price.calculated_amount",
"stock_locations.fulfillment_sets.service_zones.shipping_options.calculated_price.is_calculated_price_tax_inclusive",
],
fields: ["stock_locations.fulfillment_sets.id"],
variables: {
id: input.sales_channel_id,
"stock_locations.fulfillment_sets.service_zones.shipping_options": {
context: {
is_return: "false",
enabled_in_store: "true",
},
filters: {
address: {
city: input.shipping_address?.city,
country_code: input.shipping_address?.country_code,
province_code: input.shipping_address?.province,
},
},
},
"stock_locations.fulfillment_sets.service_zones.shipping_options.calculated_price":
{
context: {
currency_code: input.currency_code,
},
},
},
})
}).config({ name: "sales_channels-fulfillment-query" })
const shippingOptionsWithPrice = transform(
const fulfillmentSetIds = transform(
{ options: scLocationFulfillmentSets },
(data) => {
const optionsMissingPrices: string[] = []
const fulfillmentSetIds = new Set<string>()
const options = deepFlatMap(
deepFlatMap(
data.options,
"stock_locations.fulfillment_sets.service_zones.shipping_options.calculated_price",
({ shipping_options }) => {
const { calculated_price, ...options } = shipping_options ?? {}
if (
options?.id &&
!isPresent(calculated_price?.calculated_amount)
) {
optionsMissingPrices.push(options.id)
}
return {
...options,
amount: calculated_price?.calculated_amount,
is_tax_inclusive:
!!calculated_price?.is_calculated_price_tax_inclusive,
"stock_locations.fulfillment_sets",
({ fulfillment_sets }) => {
if (fulfillment_sets?.id) {
fulfillmentSetIds.add(fulfillment_sets.id)
}
}
)
return Array.from(fulfillmentSetIds)
}
)
const shippingOptions = useRemoteQueryStep({
entry_point: "shipping_options",
fields: [
"id",
"name",
"price_type",
"service_zone_id",
"shipping_profile_id",
"provider_id",
"data",
"amount",
"type.id",
"type.label",
"type.description",
"type.code",
"provider.id",
"provider.is_enabled",
"rules.attribute",
"rules.value",
"rules.operator",
"calculated_price.*",
],
variables: {
context: {
is_return: input.is_return,
enabled_in_store: "true",
},
filters: {
fulfillment_set_id: fulfillmentSetIds,
address: {
city: input.shipping_address?.city,
country_code: input.shipping_address?.country_code,
province_code: input.shipping_address?.province,
},
},
calculated_price: {
context: {
currency_code: input.currency_code,
},
},
},
}).config({ name: "shipping-options-query" })
const shippingOptionsWithPrice = transform(
{
shippingOptions,
},
(data) => {
const optionsMissingPrices: string[] = []
const options = data.shippingOptions.map((shippingOption) => {
const { calculated_price, ...options } = shippingOption ?? {}
if (options?.id && !isPresent(calculated_price?.calculated_amount)) {
optionsMissingPrices.push(options.id)
}
return {
...options,
amount: calculated_price?.calculated_amount,
is_tax_inclusive:
!!calculated_price?.is_calculated_price_tax_inclusive,
}
})
if (optionsMissingPrices.length) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
@@ -104,6 +104,7 @@ export interface CartWorkflowDTO extends CartDTO {
export interface ListShippingOptionsForCartWorkflowInputDTO {
cart_id: string
is_return?: boolean
sales_channel_id?: string
currency_code: string
shipping_address: {
@@ -2,12 +2,15 @@ import { listShippingOptionsForCartWorkflow } from "@medusajs/core-flows"
import { HttpTypes, ICartModuleService } from "@medusajs/framework/types"
import { MedusaError, Modules } from "@medusajs/framework/utils"
import { MedusaRequest, MedusaResponse } from "../../../types/routing"
import { StoreGetShippingOptionsType } from "./validators"
export const GET = async (
req: MedusaRequest<HttpTypes.StoreGetShippingOptionList>,
res: MedusaResponse<HttpTypes.StoreShippingOptionListResponse>
) => {
const { cart_id } = req.filterableFields as { cart_id: string }
const { cart_id, is_return } =
req.filterableFields as StoreGetShippingOptionsType
if (!cart_id) {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
@@ -34,6 +37,7 @@ export const GET = async (
cart_id: cart.id,
sales_channel_id: cart.sales_channel_id,
currency_code: cart.currency_code,
is_return: !!is_return,
shipping_address: {
city: cart.shipping_address?.city,
country_code: cart.shipping_address?.country_code,
@@ -10,6 +10,7 @@ export const StoreGetShippingOptions = createFindParams({
}).merge(
z.object({
cart_id: z.string(),
is_return: z.boolean().optional(),
$and: z.lazy(() => StoreGetShippingOptions.array()).optional(),
$or: z.lazy(() => StoreGetShippingOptions.array()).optional(),
})
@@ -1,9 +1,9 @@
import {
MedusaError,
RuleOperator,
isObject,
isString,
MedusaError,
pickValueFromObject,
RuleOperator,
} from "@medusajs/framework/utils"
/**
@@ -81,6 +81,7 @@ export function isContextValid(
const predicate = (rule) => {
const { attribute, operator, value } = rule
const contextValue = pickValueFromObject(attribute, context)
return operatorsPredicate[operator](
contextValue,
value as string & string[]