feat: Add shipping method to cart (#6661)

This commit is contained in:
Oli Juhl
2024-03-12 17:52:48 +01:00
committed by GitHub
parent c3c4f49fc2
commit f0c8142635
18 changed files with 491 additions and 23 deletions

View File

@@ -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))
}
)

View File

@@ -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(

View File

@@ -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)
}
)

View File

@@ -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"

View File

@@ -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,
})
}
)

View File

@@ -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 })

View File

@@ -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"

View File

@@ -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]) => {

View File

@@ -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>[])
}

View File

@@ -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"

View File

@@ -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",
},
},
],
}

View File

@@ -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",

View File

@@ -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

View File

@@ -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)

View File

@@ -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`
)
}