feat: List shipping options for cart (#6677)

This commit is contained in:
Oli Juhl authored and GitHub committed 2024-03-13 09:08:47 +00:00
1 parent db26dafa85
commit 25d176b851
23 files changed
+753 -40

No files matched your search

+6
View File
@@ -0,0 +1,6 @@
---
"@medusajs/medusa": patch
"@medusajs/orchestration": patch
---
feat: List shipping options for cart
@@ -7,6 +7,7 @@ import {
deleteLineItemsWorkflow, deleteLineItemsWorkflow,
findOrCreateCustomerStepId, findOrCreateCustomerStepId,
linkCartAndPaymentCollectionsStepId, linkCartAndPaymentCollectionsStepId,
listShippingOptionsForCartWorkflow,
refreshPaymentCollectionForCartWorkflow, refreshPaymentCollectionForCartWorkflow,
updateLineItemInCartWorkflow, updateLineItemInCartWorkflow,
updateLineItemsStepId, updateLineItemsStepId,
@@ -22,7 +23,9 @@ import {
IProductModuleService, IProductModuleService,
IRegionModuleService, IRegionModuleService,
ISalesChannelModuleService, ISalesChannelModuleService,
IStockLocationServiceNext,
} from "@medusajs/types" } from "@medusajs/types"
import { ContainerRegistrationKeys } from "@medusajs/utils"
import { medusaIntegrationTestRunner } from "medusa-test-utils" import { medusaIntegrationTestRunner } from "medusa-test-utils"
import adminSeeder from "../../../../helpers/admin-seeder" import adminSeeder from "../../../../helpers/admin-seeder"
@@ -43,6 +46,7 @@ medusaIntegrationTestRunner({
let pricingModule: IPricingModuleService let pricingModule: IPricingModuleService
let paymentModule: IPaymentModuleService let paymentModule: IPaymentModuleService
let fulfillmentModule: IFulfillmentModuleService let fulfillmentModule: IFulfillmentModuleService
let locationModule: IStockLocationServiceNext
let remoteLink, remoteQuery let remoteLink, remoteQuery
let defaultRegion let defaultRegion
@@ -63,8 +67,13 @@ medusaIntegrationTestRunner({
fulfillmentModule = appContainer.resolve( fulfillmentModule = appContainer.resolve(
ModuleRegistrationName.FULFILLMENT ModuleRegistrationName.FULFILLMENT
) )
remoteLink = appContainer.resolve("remoteLink") locationModule = appContainer.resolve(
remoteQuery = appContainer.resolve("remoteQuery") ModuleRegistrationName.STOCK_LOCATION
)
remoteLink = appContainer.resolve(ContainerRegistrationKeys.REMOTE_LINK)
remoteQuery = appContainer.resolve(
ContainerRegistrationKeys.REMOTE_QUERY
)
}) })
beforeEach(async () => { beforeEach(async () => {
@@ -672,6 +681,7 @@ medusaIntegrationTestRunner({
expect(updatedItem).not.toBeUndefined() expect(updatedItem).not.toBeUndefined()
}) })
}) })
})
describe("createPaymentCollectionForCart", () => { describe("createPaymentCollectionForCart", () => {
it("should create a payment collection and link it to cart", async () => { it("should create a payment collection and link it to cart", async () => {
@@ -810,7 +820,6 @@ medusaIntegrationTestRunner({
}) })
}) })
}) })
})
describe("refreshPaymentCollectionForCart", () => { describe("refreshPaymentCollectionForCart", () => {
it("should refresh a payment collection for a cart", async () => { it("should refresh a payment collection for a cart", async () => {
@@ -995,24 +1004,26 @@ medusaIntegrationTestRunner({
name: "Test", name: "Test",
type: "default", type: "default",
}) })
const fulfillmentSet = await fulfillmentModule.create({ const fulfillmentSet = await fulfillmentModule.create({
name: "Test", name: "Test",
type: "test-type", type: "test-type",
}) service_zones: [
const serviceZone = await fulfillmentModule.createServiceZones({ {
name: "Test", name: "Test",
fulfillment_set_id: fulfillmentSet.id,
geo_zones: [ geo_zones: [
{ {
type: "country", type: "country",
country_code: "us", country_code: "us",
}, },
], ],
},
],
}) })
const shippingOption = await fulfillmentModule.createShippingOptions({ const shippingOption = await fulfillmentModule.createShippingOptions({
name: "Test shipping option", name: "Test shipping option",
service_zone_id: serviceZone.id, service_zone_id: fulfillmentSet.service_zones[0].id,
shipping_profile_id: shippingProfile.id, shipping_profile_id: shippingProfile.id,
provider_id: "manual_test-provider", provider_id: "manual_test-provider",
price_type: "flat", price_type: "flat",
@@ -1077,6 +1088,337 @@ medusaIntegrationTestRunner({
) )
}) })
}) })
describe("listShippingOptionsForCartWorkflow", () => {
it("should list shipping options for cart", async () => {
const salesChannel = await scModuleService.create({
name: "Webshop",
})
const location = await locationModule.create({
name: "Europe",
})
let cart = await cartModuleService.create({
currency_code: "usd",
sales_channel_id: salesChannel.id,
shipping_address: {
city: "CPH",
province: "Sjaelland",
country_code: "dk",
},
})
const shippingProfile =
await fulfillmentModule.createShippingProfiles({
name: "Test",
type: "default",
})
const fulfillmentSet = await fulfillmentModule.create({
name: "Test",
type: "test-type",
service_zones: [
{
name: "Test",
geo_zones: [
{
type: "country",
country_code: "us",
},
],
},
],
})
const shippingOption = await fulfillmentModule.createShippingOptions({
name: "Test shipping option",
service_zone_id: fulfillmentSet.service_zones[0].id,
shipping_profile_id: shippingProfile.id,
provider_id: "manual_test-provider",
price_type: "flat",
type: {
label: "Test type",
description: "Test description",
code: "test-code",
},
})
const priceSet = await pricingModule.create({
prices: [
{
amount: 3000,
currency_code: "usd",
},
],
})
await remoteLink.create([
{
[Modules.SALES_CHANNEL]: {
sales_channel_id: salesChannel.id,
},
[Modules.STOCK_LOCATION]: {
stock_location_id: location.id,
},
},
{
[Modules.FULFILLMENT]: {
fulfillment_set_id: fulfillmentSet.id,
},
[Modules.STOCK_LOCATION]: {
stock_location_id: location.id,
},
},
{
[Modules.FULFILLMENT]: {
shipping_option_id: shippingOption.id,
},
[Modules.PRICING]: {
price_set_id: priceSet.id,
},
},
])
cart = await cartModuleService.retrieve(cart.id, {
select: ["id"],
relations: ["shipping_address"],
})
const { result } = await listShippingOptionsForCartWorkflow(
appContainer
).run({
input: {
cart_id: cart.id,
sales_channel_id: salesChannel.id,
currency_code: "usd",
shipping_address: {
city: cart.shipping_address?.city,
province: cart.shipping_address?.province,
country_code: cart.shipping_address?.country_code,
},
},
})
expect(result).toEqual([
expect.objectContaining({
amount: 3000,
name: "Test shipping option",
id: shippingOption.id,
}),
])
})
it("should list no shipping options for cart, if sales channel is not associated with location", async () => {
const salesChannel = await scModuleService.create({
name: "Webshop",
})
const location = await locationModule.create({
name: "Europe",
})
let cart = await cartModuleService.create({
currency_code: "usd",
sales_channel_id: salesChannel.id,
shipping_address: {
city: "CPH",
province: "Sjaelland",
country_code: "dk",
},
})
const shippingProfile =
await fulfillmentModule.createShippingProfiles({
name: "Test",
type: "default",
})
const fulfillmentSet = await fulfillmentModule.create({
name: "Test",
type: "test-type",
service_zones: [
{
name: "Test",
geo_zones: [
{
type: "country",
country_code: "us",
},
],
},
],
})
const shippingOption = await fulfillmentModule.createShippingOptions({
name: "Test shipping option",
service_zone_id: fulfillmentSet.service_zones[0].id,
shipping_profile_id: shippingProfile.id,
provider_id: "manual_test-provider",
price_type: "flat",
type: {
label: "Test type",
description: "Test description",
code: "test-code",
},
})
const priceSet = await pricingModule.create({
prices: [
{
amount: 3000,
currency_code: "usd",
},
],
})
await remoteLink.create([
{
[Modules.FULFILLMENT]: {
fulfillment_set_id: fulfillmentSet.id,
},
[Modules.STOCK_LOCATION]: {
stock_location_id: location.id,
},
},
{
[Modules.FULFILLMENT]: {
shipping_option_id: shippingOption.id,
},
[Modules.PRICING]: {
price_set_id: priceSet.id,
},
},
])
cart = await cartModuleService.retrieve(cart.id, {
select: ["id"],
relations: ["shipping_address"],
})
const { result } = await listShippingOptionsForCartWorkflow(
appContainer
).run({
input: {
cart_id: cart.id,
sales_channel_id: salesChannel.id,
currency_code: "usd",
shipping_address: {
city: cart.shipping_address?.city,
province: cart.shipping_address?.province,
country_code: cart.shipping_address?.country_code,
},
},
})
expect(result).toEqual([])
})
it("should throw when shipping options are missing prices", async () => {
const salesChannel = await scModuleService.create({
name: "Webshop",
})
const location = await locationModule.create({
name: "Europe",
})
let cart = await cartModuleService.create({
currency_code: "usd",
sales_channel_id: salesChannel.id,
shipping_address: {
city: "CPH",
province: "Sjaelland",
country_code: "dk",
},
})
const shippingProfile =
await fulfillmentModule.createShippingProfiles({
name: "Test",
type: "default",
})
const fulfillmentSet = await fulfillmentModule.create({
name: "Test",
type: "test-type",
service_zones: [
{
name: "Test",
geo_zones: [
{
type: "country",
country_code: "us",
},
],
},
],
})
const shippingOption = await fulfillmentModule.createShippingOptions({
name: "Test shipping option",
service_zone_id: fulfillmentSet.service_zones[0].id,
shipping_profile_id: shippingProfile.id,
provider_id: "manual_test-provider",
price_type: "flat",
type: {
label: "Test type",
description: "Test description",
code: "test-code",
},
})
await remoteLink.create([
{
[Modules.SALES_CHANNEL]: {
sales_channel_id: salesChannel.id,
},
[Modules.STOCK_LOCATION]: {
stock_location_id: location.id,
},
},
{
[Modules.FULFILLMENT]: {
fulfillment_set_id: fulfillmentSet.id,
},
[Modules.STOCK_LOCATION]: {
stock_location_id: location.id,
},
},
])
cart = await cartModuleService.retrieve(cart.id, {
select: ["id"],
relations: ["shipping_address"],
})
const { errors } = await listShippingOptionsForCartWorkflow(
appContainer
).run({
input: {
cart_id: cart.id,
sales_channel_id: salesChannel.id,
currency_code: "usd",
shipping_address: {
city: cart.shipping_address?.city,
province: cart.shipping_address?.province,
country_code: cart.shipping_address?.country_code,
},
},
throwOnError: false,
})
expect(errors).toEqual([
{
action: "get-shipping-option-price-sets",
error: new Error(
`Shipping options with IDs ${shippingOption.id} do not have a price`
),
handlerType: "invoke",
},
])
})
})
}) })
}, },
}) })
@@ -0,0 +1,87 @@
import { ModuleRegistrationName, Modules } from "@medusajs/modules-sdk"
import {
IFulfillmentModuleService,
ISalesChannelModuleService,
IStockLocationServiceNext,
} from "@medusajs/types"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { medusaIntegrationTestRunner } from "medusa-test-utils"
jest.setTimeout(50000)
const env = { MEDUSA_FF_MEDUSA_V2: true }
medusaIntegrationTestRunner({
env,
testSuite: ({ getContainer }) => {
describe("FulfillmentSet and Location", () => {
let appContainer
let fulfillmentModule: IFulfillmentModuleService
let locationModule: IStockLocationServiceNext
let scService: ISalesChannelModuleService
let remoteQuery
let remoteLink
beforeAll(async () => {
appContainer = getContainer()
fulfillmentModule = appContainer.resolve(
ModuleRegistrationName.FULFILLMENT
)
locationModule = appContainer.resolve(
ModuleRegistrationName.STOCK_LOCATION
)
scService = appContainer.resolve(ModuleRegistrationName.SALES_CHANNEL)
remoteQuery = appContainer.resolve(
ContainerRegistrationKeys.REMOTE_QUERY
)
remoteLink = appContainer.resolve(ContainerRegistrationKeys.REMOTE_LINK)
})
it("should query fulfillment set and location link with remote query", async () => {
const fulfillmentSet = await fulfillmentModule.create({
name: "Test fulfillment set",
type: "delivery",
})
const euWarehouse = await locationModule.create({
name: "EU Warehouse",
})
await remoteLink.create([
{
[Modules.FULFILLMENT]: {
fulfillment_set_id: fulfillmentSet.id,
},
[Modules.STOCK_LOCATION]: {
stock_location_id: euWarehouse.id,
},
},
])
const linkQuery = remoteQueryObjectFromString({
entryPoint: "fulfillment_sets",
fields: ["id", "stock_locations.id"],
})
const link = await remoteQuery(linkQuery)
expect(link).toHaveLength(1)
expect(link).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: fulfillmentSet.id,
stock_locations: expect.arrayContaining([
expect.objectContaining({
id: euWarehouse.id,
}),
]),
}),
])
)
})
})
},
})
@@ -56,7 +56,7 @@ medusaIntegrationTestRunner({
sales_channel_id: scWebshop.id, sales_channel_id: scWebshop.id,
}, },
[Modules.STOCK_LOCATION]: { [Modules.STOCK_LOCATION]: {
location_id: euWarehouse.id, stock_location_id: euWarehouse.id,
}, },
}, },
{ {
@@ -64,7 +64,7 @@ medusaIntegrationTestRunner({
sales_channel_id: scCphStore.id, sales_channel_id: scCphStore.id,
}, },
[Modules.STOCK_LOCATION]: { [Modules.STOCK_LOCATION]: {
location_id: euWarehouse.id, stock_location_id: euWarehouse.id,
}, },
}, },
{ {
@@ -72,7 +72,7 @@ medusaIntegrationTestRunner({
sales_channel_id: scNycStore.id, sales_channel_id: scNycStore.id,
}, },
[Modules.STOCK_LOCATION]: { [Modules.STOCK_LOCATION]: {
location_id: usWarehouse.id, stock_location_id: usWarehouse.id,
}, },
}, },
]) ])
@@ -3,6 +3,7 @@ import { IPricingModuleService, PricingContext } from "@medusajs/types"
import { import {
ContainerRegistrationKeys, ContainerRegistrationKeys,
MedusaError, MedusaError,
arrayDifference,
remoteQueryObjectFromString, remoteQueryObjectFromString,
} from "@medusajs/utils" } from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk" import { StepResponse, createStep } from "@medusajs/workflows-sdk"
@@ -12,7 +13,7 @@ interface StepInput {
context?: Record<string, unknown> context?: Record<string, unknown>
} }
export const getShippingOptionPriceSetsStepId = "get-variant-price-sets" export const getShippingOptionPriceSetsStepId = "get-shipping-option-price-sets"
export const getShippingOptionPriceSetsStep = createStep( export const getShippingOptionPriceSetsStep = createStep(
getShippingOptionPriceSetsStepId, getShippingOptionPriceSetsStepId,
async (data: StepInput, { container }) => { async (data: StepInput, { container }) => {
@@ -38,26 +39,22 @@ export const getShippingOptionPriceSetsStep = createStep(
const optionPriceSets = await remoteQuery(query) const optionPriceSets = await remoteQuery(query)
const notFound: string[] = [] const optionsMissingPrices = arrayDifference(
const priceSetIds: string[] = [] data.optionIds,
optionPriceSets.map((v) => v.shipping_option_id)
)
optionPriceSets.forEach((v) => { if (optionsMissingPrices.length) {
if (v.price_set_id) {
priceSetIds.push(v.price_set_id)
} else {
notFound.push(v.shipping_option_id)
}
})
if (notFound.length) {
throw new MedusaError( throw new MedusaError(
MedusaError.Types.INVALID_DATA, MedusaError.Types.INVALID_DATA,
`Shipping options with IDs ${notFound.join(", ")} do not have a price` `Shipping options with IDs ${optionsMissingPrices.join(
", "
)} do not have a price`
) )
} }
const calculatedPriceSets = await pricingModuleService.calculatePrices( const calculatedPriceSets = await pricingModuleService.calculatePrices(
{ id: priceSetIds }, { id: optionPriceSets.map((v) => v.price_set_id) },
{ context: data.context as PricingContext["context"] } { context: data.context as PricingContext["context"] }
) )
@@ -2,6 +2,7 @@ export * from "./add-shipping-method-to-cart"
export * from "./add-to-cart" export * from "./add-to-cart"
export * from "./create-carts" export * from "./create-carts"
export * from "./create-payment-collection-for-cart" export * from "./create-payment-collection-for-cart"
export * from "./list-shipping-options-for-cart"
export * from "./refresh-payment-collection" export * from "./refresh-payment-collection"
export * from "./update-cart" export * from "./update-cart"
export * from "./update-cart-promotions" export * from "./update-cart-promotions"
@@ -0,0 +1,93 @@
import { ListShippingOptionsForCartWorkflowInputDTO } from "@medusajs/types"
import {
WorkflowData,
createWorkflow,
transform,
} from "@medusajs/workflows-sdk"
import { useRemoteQueryStep } from "../../../common/steps/use-remote-query"
import { listShippingOptionsForContextStep } from "../../../shipping-options"
import { getShippingOptionPriceSetsStep } from "../steps"
export const listShippingOptionsForCartWorkflowId =
"list-shipping-options-for-cart"
export const listShippingOptionsForCartWorkflow = createWorkflow(
listShippingOptionsForCartWorkflowId,
(input: WorkflowData<ListShippingOptionsForCartWorkflowInputDTO>) => {
const scLocationFulfillmentSets = useRemoteQueryStep({
entry_point: "sales_channels",
fields: ["stock_locations.fulfillment_sets.id"],
variables: { id: input.sales_channel_id },
})
const listOptionsInput = transform(
{ scLocationFulfillmentSets, input },
(data) => {
const fulfillmentSetIds = data.scLocationFulfillmentSets
.map((sc) =>
sc.stock_locations.map((loc) =>
loc.fulfillment_sets.map(({ id }) => id)
)
)
.flat(2)
return {
context: {
fulfillment_set_id: fulfillmentSetIds,
service_zone: {
geo_zones: {
city: data.input.shipping_address?.city,
country_code: data.input.shipping_address?.country_code,
province_code: data.input.shipping_address?.province,
},
},
},
config: {
select: [
"id",
"name",
"price_type",
"service_zone_id",
"shipping_profile_id",
"provider_id",
"data",
"amount",
],
relations: ["type", "provider"],
},
}
}
)
const options = listShippingOptionsForContextStep(listOptionsInput)
const optionIds = transform({ options }, (data) =>
data.options.map((option) => option.id)
)
// TODO: Separate shipping options based on price_type, flat_rate vs calculated
const priceSets = getShippingOptionPriceSetsStep({
optionIds,
context: {
currency_code: input.currency_code,
},
})
const shippingOptionsWithPrice = transform(
{ priceSets, options },
(data) => {
const options = data.options.map((option) => {
const price = data.priceSets?.[option.id].calculated_amount
return {
...option,
amount: price,
}
})
return options
}
)
return shippingOptionsWithPrice
}
)
+1
View File
@@ -9,6 +9,7 @@ export * from "./payment"
export * from "./product" export * from "./product"
export * from "./promotion" export * from "./promotion"
export * from "./region" export * from "./region"
export * from "./shipping-options"
export * from "./store" export * from "./store"
export * from "./tax" export * from "./tax"
export * from "./user" export * from "./user"
@@ -0,0 +1 @@
export * from "./steps"
@@ -0,0 +1 @@
export * from "./list-shipping-options-for-context"
@@ -0,0 +1,30 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import {
FindConfig,
IFulfillmentModuleService,
ShippingOptionDTO,
} from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
interface StepInput {
context: Record<string, unknown>
config?: FindConfig<ShippingOptionDTO>
}
export const listShippingOptionsForContextStepId =
"list-shipping-options-for-context"
export const listShippingOptionsForContextStep = createStep(
listShippingOptionsForContextStepId,
async (data: StepInput, { container }) => {
const fulfillmentService = container.resolve<IFulfillmentModuleService>(
ModuleRegistrationName.FULFILLMENT
)
const shippingOptions = await fulfillmentService.listShippingOptions(
data.context,
data.config
)
return new StepResponse(shippingOptions)
}
)
+1
View File
@@ -10,3 +10,4 @@ export { default as ShippingOption } from "./shipping-option"
export { default as ShippingOptionRule } from "./shipping-option-rule" export { default as ShippingOptionRule } from "./shipping-option-rule"
export { default as ShippingOptionType } from "./shipping-option-type" export { default as ShippingOptionType } from "./shipping-option-type"
export { default as ShippingProfile } from "./shipping-profile" export { default as ShippingProfile } from "./shipping-profile"
@@ -0,0 +1,63 @@
import { Modules } from "@medusajs/modules-sdk"
import { ModuleJoinerConfig } from "@medusajs/types"
import { LINKS } from "../links"
export const FulfillmentSetLocation: ModuleJoinerConfig = {
serviceName: LINKS.FulfillmentSetLocation,
isLink: true,
databaseConfig: {
tableName: "fulfillment_set_location",
idPrefix: "fsloc",
},
alias: [
{
name: ["fulfillment_set_location", "fulfillment_set_locations"],
args: {
entity: "LinkFulfillmentSetLocation",
},
},
],
primaryKeys: ["id", "fulfillment_set_id", "stock_location_id"],
relationships: [
{
serviceName: Modules.FULFILLMENT,
primaryKey: "id",
foreignKey: "fulfillment_set_id",
alias: "fulfillment_set",
},
{
serviceName: Modules.STOCK_LOCATION,
primaryKey: "id",
foreignKey: "stock_location_id",
alias: "location",
},
],
extends: [
{
serviceName: Modules.FULFILLMENT,
fieldAlias: {
stock_locations: "locations_link.location",
},
relationship: {
serviceName: LINKS.FulfillmentSetLocation,
primaryKey: "fulfillment_set_id",
foreignKey: "id",
alias: "locations_link",
isList: true,
},
},
{
serviceName: Modules.STOCK_LOCATION,
relationship: {
serviceName: LINKS.FulfillmentSetLocation,
primaryKey: "stock_location_id",
foreignKey: "id",
alias: "fulfillment_set_link",
isList: true,
},
fieldAlias: {
fulfillment_sets: "fulfillment_set_link.fulfillment_set",
},
},
],
}
@@ -3,6 +3,7 @@ export * from "./cart-payment-collection"
export * from "./cart-promotion" export * from "./cart-promotion"
export * from "./cart-region" export * from "./cart-region"
export * from "./cart-sales-channel" export * from "./cart-sales-channel"
export * from "./fulfillment-set-location"
export * from "./inventory-level-stock-location" export * from "./inventory-level-stock-location"
export * from "./order-sales-channel" export * from "./order-sales-channel"
export * from "./product-sales-channel" export * from "./product-sales-channel"
@@ -17,7 +17,7 @@ export const SalesChannelLocation: ModuleJoinerConfig = {
}, },
}, },
], ],
primaryKeys: ["id", "sales_channel_id", "location_id"], primaryKeys: ["id", "sales_channel_id", "stock_location_id"],
relationships: [ relationships: [
{ {
serviceName: Modules.SALES_CHANNEL, serviceName: Modules.SALES_CHANNEL,
@@ -28,7 +28,7 @@ export const SalesChannelLocation: ModuleJoinerConfig = {
{ {
serviceName: Modules.STOCK_LOCATION, serviceName: Modules.STOCK_LOCATION,
primaryKey: "id", primaryKey: "id",
foreignKey: "location_id", foreignKey: "stock_location_id",
alias: "location", alias: "location",
}, },
], ],
@@ -36,7 +36,7 @@ export const SalesChannelLocation: ModuleJoinerConfig = {
{ {
serviceName: Modules.SALES_CHANNEL, serviceName: Modules.SALES_CHANNEL,
fieldAlias: { fieldAlias: {
locations: "locations_link.location", stock_locations: "locations_link.location",
}, },
relationship: { relationship: {
serviceName: LINKS.SalesChannelLocation, serviceName: LINKS.SalesChannelLocation,
@@ -53,7 +53,7 @@ export const SalesChannelLocation: ModuleJoinerConfig = {
}, },
relationship: { relationship: {
serviceName: LINKS.SalesChannelLocation, serviceName: LINKS.SalesChannelLocation,
primaryKey: "location_id", primaryKey: "stock_location_id",
foreignKey: "id", foreignKey: "id",
alias: "sales_channels_link", alias: "sales_channels_link",
isList: true, isList: true,
+6
View File
@@ -44,6 +44,12 @@ export const LINKS = {
Modules.STOCK_LOCATION, Modules.STOCK_LOCATION,
"location_id" "location_id"
), ),
FulfillmentSetLocation: composeLinkName(
Modules.FULFILLMENT,
"fulfillment_set_id",
Modules.STOCK_LOCATION,
"location_id"
),
// Internal services // Internal services
ProductShippingProfile: composeLinkName( ProductShippingProfile: composeLinkName(
@@ -0,0 +1,42 @@
import { listShippingOptionsForCartWorkflow } from "@medusajs/core-flows"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { ICartModuleService } from "@medusajs/types"
import { MedusaRequest, MedusaResponse } from "../../../../types/routing"
export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
const { cart_id } = req.params
const cartService = req.scope.resolve<ICartModuleService>(
ModuleRegistrationName.CART
)
const cart = await cartService.retrieve(cart_id, {
select: [
"id",
"sales_channel_id",
"subtotal",
"currency_code",
"shipping_address.city",
"shipping_address.country_code",
"shipping_address.province",
],
relations: ["shipping_address"],
})
const shippingOptions = await listShippingOptionsForCartWorkflow(
req.scope
).run({
input: {
cart_id: cart.id,
sales_channel_id: cart.sales_channel_id,
currency_code: cart.currency_code,
shipping_address: {
city: cart.shipping_address?.city,
country_code: cart.shipping_address?.country_code,
province: cart.shipping_address?.province,
},
},
})
res.json({ shipping_options: shippingOptions })
}
@@ -0,0 +1,9 @@
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
export const storeShippingOptionRoutesMiddlewares: MiddlewareRoute[] = [
{
method: ["GET"],
matcher: "/store/shipping-options/:cart_id",
middlewares: [],
},
]
@@ -0,0 +1,14 @@
export const defaultStoreShippingOptionsFields = [
"id",
"name",
"price_type",
"service_zone_id",
"shipping_profile_id",
"fulfillment_provider_id",
"shipping_option_type_id",
]
export const listTransformQueryConfig = {
defaultLimit: 20,
isList: true,
}
@@ -777,7 +777,7 @@ export class RemoteJoiner {
// remove alias from fields // remove alias from fields
const parentPath = [BASE_PATH, ...currentPath].join(".") const parentPath = [BASE_PATH, ...currentPath].join(".")
const parentExpands = parsedExpands.get(parentPath) const parentExpands = parsedExpands.get(parentPath)
parentExpands.fields = parentExpands.fields.filter( parentExpands.fields = parentExpands.fields?.filter(
(field) => field !== property (field) => field !== property
) )
@@ -787,12 +787,13 @@ export class RemoteJoiner {
) )
) )
const parentFieldAlias = fullPath[Math.max(fullPath.length - 2, 0)]
implodeMapping.push({ implodeMapping.push({
location: [...currentPath], location: [...currentPath],
property, property,
path: fullPath, path: fullPath,
isList: !!serviceConfig.relationships?.find( isList: !!serviceConfig.relationships?.find(
(relationship) => relationship.alias === fullPath[0] (relationship) => relationship.alias === parentFieldAlias
)?.isList, )?.isList,
}) })
@@ -1,6 +1,6 @@
import { MapToConfig } from "@medusajs/utils"
import { ModuleJoinerConfig } from "@medusajs/types"
import { Modules } from "@medusajs/modules-sdk" import { Modules } from "@medusajs/modules-sdk"
import { ModuleJoinerConfig } from "@medusajs/types"
import { MapToConfig } from "@medusajs/utils"
import { StockLocation } from "./models" import { StockLocation } from "./models"
import moduleSchema from "./schema" import moduleSchema from "./schema"
+16
View File
@@ -1,4 +1,5 @@
import { CustomerDTO } from "../customer" import { CustomerDTO } from "../customer"
import { ShippingOptionDTO } from "../fulfillment"
import { ProductDTO } from "../product" import { ProductDTO } from "../product"
import { RegionDTO } from "../region" import { RegionDTO } from "../region"
import { CartDTO, CartLineItemDTO } from "./common" import { CartDTO, CartLineItemDTO } from "./common"
@@ -101,3 +102,18 @@ export interface CartWorkflowDTO extends CartDTO {
product?: ProductDTO product?: ProductDTO
region?: RegionDTO region?: RegionDTO
} }
export interface ListShippingOptionsForCartWorkflowInputDTO {
cart_id: string
sales_channel_id?: string
currency_code: string
shipping_address: {
city?: string
country_code?: string
province?: string
}
}
export interface PricedShippingOptionDTO extends ShippingOptionDTO {
amount: number
}
@@ -1,16 +1,16 @@
import { FilterableServiceZoneProps, ServiceZoneDTO } from "./service-zone" import { BaseFilterable, OperatorMap } from "../../dal"
import { ShippingProfileDTO } from "./shipping-profile" import { FulfillmentDTO } from "./fulfillment"
import { FulfillmentProviderDTO } from "./fulfillment-provider" import { FulfillmentProviderDTO } from "./fulfillment-provider"
import { import { FilterableServiceZoneProps, ServiceZoneDTO } from "./service-zone"
FilterableShippingOptionTypeProps,
ShippingOptionTypeDTO,
} from "./shipping-option-type"
import { import {
FilterableShippingOptionRuleProps, FilterableShippingOptionRuleProps,
ShippingOptionRuleDTO, ShippingOptionRuleDTO,
} from "./shipping-option-rule" } from "./shipping-option-rule"
import { BaseFilterable, OperatorMap } from "../../dal" import {
import { FulfillmentDTO } from "./fulfillment" FilterableShippingOptionTypeProps,
ShippingOptionTypeDTO,
} from "./shipping-option-type"
import { ShippingProfileDTO } from "./shipping-profile"
export type ShippingOptionPriceType = "calculated" | "flat" export type ShippingOptionPriceType = "calculated" | "flat"
@@ -40,6 +40,7 @@ export interface FilterableShippingOptionProps
id?: string | string[] | OperatorMap<string | string[]> id?: string | string[] | OperatorMap<string | string[]>
name?: string | string[] | OperatorMap<string | string[]> name?: string | string[] | OperatorMap<string | string[]>
fulfillment_set_id?: string | string[] | OperatorMap<string | string[]> fulfillment_set_id?: string | string[] | OperatorMap<string | string[]>
shipping_profile_id?: string | string[] | OperatorMap<string | string[]>
fulfillment_set_type?: string | string[] | OperatorMap<string | string[]> fulfillment_set_type?: string | string[] | OperatorMap<string | string[]>
price_type?: price_type?:
| ShippingOptionPriceType | ShippingOptionPriceType