feat: Add shipping method to cart (#6661)
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
import { CreateShippingMethodDTO, ICartModuleService } from "@medusajs/types"
|
||||
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||
import { ModuleRegistrationName } from "../../../../../modules-sdk/dist"
|
||||
|
||||
interface StepInput {
|
||||
shipping_methods: CreateShippingMethodDTO[]
|
||||
}
|
||||
|
||||
export const addShippingMethodToCartStepId = "add-shipping-method-to-cart-step"
|
||||
export const addShippingMethodToCartStep = createStep(
|
||||
addShippingMethodToCartStepId,
|
||||
async (data: StepInput, { container }) => {
|
||||
const cartService = container.resolve<ICartModuleService>(
|
||||
ModuleRegistrationName.CART
|
||||
)
|
||||
|
||||
const methods = await cartService.addShippingMethods(data.shipping_methods)
|
||||
|
||||
return new StepResponse(methods, methods)
|
||||
},
|
||||
async (methods, { container }) => {
|
||||
const cartService: ICartModuleService = container.resolve(
|
||||
ModuleRegistrationName.CART
|
||||
)
|
||||
if (!methods?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
await cartService.deleteShippingMethods(methods.map((m) => m.id))
|
||||
}
|
||||
)
|
||||
@@ -16,7 +16,7 @@ export const addToCartStep = createStep(
|
||||
|
||||
const items = await cartService.addLineItems(data.items)
|
||||
|
||||
return new StepResponse(items)
|
||||
return new StepResponse(items, items)
|
||||
},
|
||||
async (createdLineItems, { container }) => {
|
||||
const cartService: ICartModuleService = container.resolve(
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { IPricingModuleService, PricingContext } from "@medusajs/types"
|
||||
import {
|
||||
ContainerRegistrationKeys,
|
||||
MedusaError,
|
||||
remoteQueryObjectFromString,
|
||||
} from "@medusajs/utils"
|
||||
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||
|
||||
interface StepInput {
|
||||
optionIds: string[]
|
||||
context?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export const getShippingOptionPriceSetsStepId = "get-variant-price-sets"
|
||||
export const getShippingOptionPriceSetsStep = createStep(
|
||||
getShippingOptionPriceSetsStepId,
|
||||
async (data: StepInput, { container }) => {
|
||||
if (!data.optionIds.length) {
|
||||
return new StepResponse({})
|
||||
}
|
||||
|
||||
const pricingModuleService = container.resolve<IPricingModuleService>(
|
||||
ModuleRegistrationName.PRICING
|
||||
)
|
||||
|
||||
const remoteQuery = container.resolve(
|
||||
ContainerRegistrationKeys.REMOTE_QUERY
|
||||
)
|
||||
|
||||
const query = remoteQueryObjectFromString({
|
||||
entryPoint: "shipping_option_price_set",
|
||||
fields: ["id", "shipping_option_id", "price_set_id"],
|
||||
variables: {
|
||||
shipping_option_id: data.optionIds,
|
||||
},
|
||||
})
|
||||
|
||||
const optionPriceSets = await remoteQuery(query)
|
||||
|
||||
const notFound: string[] = []
|
||||
const priceSetIds: string[] = []
|
||||
|
||||
optionPriceSets.forEach((v) => {
|
||||
if (v.price_set_id) {
|
||||
priceSetIds.push(v.price_set_id)
|
||||
} else {
|
||||
notFound.push(v.shipping_option_id)
|
||||
}
|
||||
})
|
||||
|
||||
if (notFound.length) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Shipping options with IDs ${notFound.join(", ")} do not have a price`
|
||||
)
|
||||
}
|
||||
|
||||
const calculatedPriceSets = await pricingModuleService.calculatePrices(
|
||||
{ id: priceSetIds },
|
||||
{ context: data.context as PricingContext["context"] }
|
||||
)
|
||||
|
||||
const idToPriceSet = new Map<string, Record<string, any>>(
|
||||
calculatedPriceSets.map((p) => [p.id, p])
|
||||
)
|
||||
|
||||
const optionToCalculatedPriceSets = optionPriceSets.reduce(
|
||||
(acc, { shipping_option_id, price_set_id }) => {
|
||||
const calculatedPriceSet = idToPriceSet.get(price_set_id)
|
||||
if (calculatedPriceSet) {
|
||||
acc[shipping_option_id] = calculatedPriceSet
|
||||
}
|
||||
|
||||
return acc
|
||||
},
|
||||
{}
|
||||
)
|
||||
|
||||
return new StepResponse(optionToCalculatedPriceSets)
|
||||
}
|
||||
)
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from "./add-shipping-method-to-cart"
|
||||
export * from "./add-to-cart"
|
||||
export * from "./create-carts"
|
||||
export * from "./create-line-item-adjustments"
|
||||
@@ -7,6 +8,7 @@ export * from "./find-or-create-customer"
|
||||
export * from "./find-sales-channel"
|
||||
export * from "./get-actions-to-compute-from-promotions"
|
||||
export * from "./get-item-tax-lines"
|
||||
export * from "./get-shipping-option-price-sets"
|
||||
export * from "./get-variant-price-sets"
|
||||
export * from "./get-variants"
|
||||
export * from "./prepare-adjustments-from-promotion-actions"
|
||||
@@ -18,3 +20,4 @@ export * from "./set-tax-lines-for-items"
|
||||
export * from "./update-cart-promotions"
|
||||
export * from "./update-carts"
|
||||
export * from "./validate-variants-existence"
|
||||
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import {
|
||||
WorkflowData,
|
||||
createWorkflow,
|
||||
transform,
|
||||
} from "@medusajs/workflows-sdk"
|
||||
import { useRemoteQueryStep } from "../../../common/steps/use-remote-query"
|
||||
import { addShippingMethodToCartStep } from "../steps"
|
||||
import { getShippingOptionPriceSetsStep } from "../steps/get-shipping-option-price-sets"
|
||||
import { refreshCartPromotionsStep } from "../steps/refresh-cart-promotions"
|
||||
import { updateTaxLinesStep } from "../steps/update-tax-lines"
|
||||
|
||||
interface AddShippingMethodToCartWorkflowInput {
|
||||
cart_id: string
|
||||
currency_code: string
|
||||
options: {
|
||||
id: string
|
||||
data?: Record<string, unknown>
|
||||
}[]
|
||||
}
|
||||
|
||||
export const addShippingMethodToCartWorkflowId = "add-shipping-method-to-cart"
|
||||
export const addShippingMethodToWorkflow = createWorkflow(
|
||||
addShippingMethodToCartWorkflowId,
|
||||
(
|
||||
input: WorkflowData<AddShippingMethodToCartWorkflowInput>
|
||||
): WorkflowData<void> => {
|
||||
const optionIds = transform({ input }, (data) => {
|
||||
return (data.input.options ?? []).map((i) => i.id)
|
||||
})
|
||||
|
||||
const priceSets = getShippingOptionPriceSetsStep({
|
||||
optionIds: optionIds,
|
||||
context: { currency_code: input.currency_code },
|
||||
})
|
||||
|
||||
const shippingOptions = useRemoteQueryStep({
|
||||
entry_point: "shipping_option",
|
||||
fields: ["id", "name"],
|
||||
variables: {
|
||||
id: optionIds,
|
||||
},
|
||||
})
|
||||
|
||||
const shippingMethodInput = transform(
|
||||
{ priceSets, input, shippingOptions },
|
||||
(data) => {
|
||||
const options = (data.input.options ?? []).map((option) => {
|
||||
const shippingOption = data.shippingOptions.find(
|
||||
(so) => so.id === option.id
|
||||
)!
|
||||
|
||||
const price = data.priceSets[option.id].calculated_amount
|
||||
|
||||
return {
|
||||
shipping_option_id: shippingOption.id,
|
||||
amount: price,
|
||||
data: option.data ?? {},
|
||||
name: shippingOption.name,
|
||||
cart_id: data.input.cart_id,
|
||||
}
|
||||
})
|
||||
|
||||
return options
|
||||
}
|
||||
)
|
||||
|
||||
const shippingMethods = addShippingMethodToCartStep({
|
||||
shipping_methods: shippingMethodInput,
|
||||
})
|
||||
refreshCartPromotionsStep({ id: input.cart_id })
|
||||
updateTaxLinesStep({
|
||||
cart_or_cart_id: input.cart_id,
|
||||
shipping_methods: shippingMethods,
|
||||
})
|
||||
}
|
||||
)
|
||||
@@ -92,7 +92,6 @@ export const addToCartWorkflow = createWorkflow(
|
||||
updateTaxLinesStep({
|
||||
cart_or_cart_id: input.cart,
|
||||
items,
|
||||
// TODO: add shipping methods here when its ready
|
||||
})
|
||||
|
||||
refreshCartPromotionsStep({ id: input.cart.id })
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from "./add-shipping-method-to-cart"
|
||||
export * from "./add-to-cart"
|
||||
export * from "./create-carts"
|
||||
export * from "./create-payment-collection-for-cart"
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { moduleProviderLoader } from "@medusajs/modules-sdk"
|
||||
import { LoaderOptions, ModuleProvider, ModulesSdkTypes } from "@medusajs/types"
|
||||
import { asFunction, asValue, Lifetime } from "awilix"
|
||||
import { FulfillmentIdentifiersRegistrationName } from "@types"
|
||||
import {
|
||||
ContainerRegistrationKeys,
|
||||
lowerCaseFirst,
|
||||
promiseAll,
|
||||
} from "@medusajs/utils"
|
||||
import { FulfillmentProviderService } from "@services"
|
||||
import { FulfillmentIdentifiersRegistrationName } from "@types"
|
||||
import { Lifetime, asFunction, asValue } from "awilix"
|
||||
|
||||
const registrationFn = async (klass, container, pluginOptions) => {
|
||||
Object.entries(pluginOptions.config || []).map(([name, config]) => {
|
||||
|
||||
@@ -25,7 +25,6 @@ import {
|
||||
promiseAll,
|
||||
} from "@medusajs/utils"
|
||||
|
||||
import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config"
|
||||
import {
|
||||
Fulfillment,
|
||||
FulfillmentSet,
|
||||
@@ -37,6 +36,7 @@ import {
|
||||
ShippingProfile,
|
||||
} from "@models"
|
||||
import { isContextValid, validateRules } from "@utils"
|
||||
import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config"
|
||||
import FulfillmentProviderService from "./fulfillment-provider"
|
||||
|
||||
const generateMethodForModels = [
|
||||
@@ -435,7 +435,7 @@ export default class FulfillmentModuleService<
|
||||
return []
|
||||
}
|
||||
|
||||
const rules = data_.flatMap((d) => d.rules)
|
||||
const rules = data_.flatMap((d) => d.rules).filter(Boolean)
|
||||
if (rules.length) {
|
||||
validateRules(rules as Record<string, unknown>[])
|
||||
}
|
||||
|
||||
@@ -12,4 +12,5 @@ export * from "./product-variant-price-set"
|
||||
export * from "./publishable-api-key-sales-channel"
|
||||
export * from "./region-payment-provider"
|
||||
export * from "./sales-channel-location"
|
||||
export * from "./shipping-option-price-set"
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import { ModuleJoinerConfig } from "@medusajs/types"
|
||||
import { LINKS } from "../links"
|
||||
|
||||
export const ShippingOptionPriceSet: ModuleJoinerConfig = {
|
||||
serviceName: LINKS.ShippingOptionPriceSet,
|
||||
isLink: true,
|
||||
databaseConfig: {
|
||||
tableName: "shipping_option_price_set",
|
||||
idPrefix: "sops",
|
||||
},
|
||||
alias: [
|
||||
{
|
||||
name: ["shipping_option_price_set", "shipping_option_price_sets"],
|
||||
args: {
|
||||
entity: "LinkShippingOptionPriceSet",
|
||||
},
|
||||
},
|
||||
],
|
||||
primaryKeys: ["id", "shipping_option_id", "price_set_id"],
|
||||
relationships: [
|
||||
{
|
||||
serviceName: Modules.FULFILLMENT,
|
||||
primaryKey: "id",
|
||||
foreignKey: "shipping_option_id",
|
||||
alias: "shipping_option",
|
||||
args: {
|
||||
methodSuffix: "ShippingOptions",
|
||||
},
|
||||
},
|
||||
{
|
||||
serviceName: Modules.PRICING,
|
||||
primaryKey: "id",
|
||||
foreignKey: "price_set_id",
|
||||
alias: "price_set",
|
||||
deleteCascade: true,
|
||||
},
|
||||
],
|
||||
extends: [
|
||||
{
|
||||
serviceName: Modules.FULFILLMENT,
|
||||
relationship: {
|
||||
serviceName: LINKS.ShippingOptionPriceSet,
|
||||
primaryKey: "shipping_option_id",
|
||||
foreignKey: "id",
|
||||
alias: "price",
|
||||
},
|
||||
},
|
||||
{
|
||||
serviceName: Modules.PRICING,
|
||||
relationship: {
|
||||
serviceName: LINKS.ShippingOptionPriceSet,
|
||||
primaryKey: "price_set_id",
|
||||
foreignKey: "id",
|
||||
alias: "shipping_option_link",
|
||||
},
|
||||
fieldAlias: {
|
||||
shipping_option: "shipping_option_link.shipping_option",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -14,6 +14,12 @@ export const LINKS = {
|
||||
Modules.PRICING,
|
||||
"price_set_id"
|
||||
),
|
||||
ShippingOptionPriceSet: composeLinkName(
|
||||
Modules.FULFILLMENT,
|
||||
"shipping_option_id",
|
||||
Modules.PRICING,
|
||||
"price_set_id"
|
||||
),
|
||||
CartPaymentCollection: composeLinkName(
|
||||
Modules.CART,
|
||||
"cart_id",
|
||||
|
||||
@@ -15,15 +15,15 @@ import {
|
||||
RuleTypeDTO,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
arrayDifference,
|
||||
deduplicate,
|
||||
groupBy,
|
||||
InjectManager,
|
||||
InjectTransactionManager,
|
||||
MedusaContext,
|
||||
MedusaError,
|
||||
ModulesSdkUtils,
|
||||
PriceListType,
|
||||
arrayDifference,
|
||||
deduplicate,
|
||||
groupBy,
|
||||
removeNullish,
|
||||
} from "@medusajs/utils"
|
||||
|
||||
@@ -47,9 +47,9 @@ import {
|
||||
PriceRuleService,
|
||||
RuleTypeService,
|
||||
} from "@services"
|
||||
import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config"
|
||||
import { validatePriceListDates } from "@utils"
|
||||
import { ServiceTypes } from "@types"
|
||||
import { validatePriceListDates } from "@utils"
|
||||
import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config"
|
||||
|
||||
type InjectedDependencies = {
|
||||
baseRepository: DAL.RepositoryService
|
||||
|
||||
@@ -21,11 +21,7 @@ import {
|
||||
} from "@mikro-orm/core/typings"
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
import { isString } from "../../common"
|
||||
import {
|
||||
InjectTransactionManager,
|
||||
MedusaContext,
|
||||
buildQuery,
|
||||
} from "../../modules-sdk"
|
||||
import { buildQuery } from "../../modules-sdk"
|
||||
import {
|
||||
getSoftDeletedCascadedEntitiesIdsMappedBy,
|
||||
transactionWrapper,
|
||||
@@ -119,10 +115,9 @@ export class MikroOrmBaseRepository<T extends object = object>
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
|
||||
@InjectTransactionManager()
|
||||
async softDelete(
|
||||
idsOrFilter: string[] | InternalFilterQuery,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
sharedContext: Context = {}
|
||||
): Promise<[T[], Record<string, unknown[]>]> {
|
||||
const isArray = Array.isArray(idsOrFilter)
|
||||
// TODO handle composite keys
|
||||
@@ -152,10 +147,9 @@ export class MikroOrmBaseRepository<T extends object = object>
|
||||
return [entities, softDeletedEntitiesMap]
|
||||
}
|
||||
|
||||
@InjectTransactionManager()
|
||||
async restore(
|
||||
idsOrFilter: string[] | InternalFilterQuery,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
sharedContext: Context = {}
|
||||
): Promise<[T[], Record<string, unknown[]>]> {
|
||||
// TODO handle composite keys
|
||||
const isArray = Array.isArray(idsOrFilter)
|
||||
|
||||
@@ -15,7 +15,9 @@ export function InjectTransactionManager(
|
||||
): void {
|
||||
if (!target.MedusaContextIndex_) {
|
||||
throw new Error(
|
||||
`To apply @InjectTransactionManager you have to flag a parameter using @MedusaContext`
|
||||
`An error occured applying decorator '@InjectTransactionManager' to method ${String(
|
||||
propertyKey
|
||||
)}: Missing parameter with flag @MedusaContext`
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user