feat: Add create location fulfillment set flow + API (#6945)
This commit is contained in:
@@ -4,8 +4,8 @@ import {
|
||||
createAdminUser,
|
||||
} from "../../../../helpers/create-admin-user"
|
||||
|
||||
import { ContainerRegistrationKeys } from "@medusajs/utils"
|
||||
import { IStockLocationServiceNext } from "@medusajs/types"
|
||||
import { ContainerRegistrationKeys } from "@medusajs/utils"
|
||||
|
||||
const { medusaIntegrationTestRunner } = require("medusa-test-utils")
|
||||
|
||||
@@ -374,5 +374,59 @@ medusaIntegrationTestRunner({
|
||||
).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Location fulfillment sets", () => {
|
||||
let stockLocationId
|
||||
|
||||
beforeEach(async () => {
|
||||
const createResponse = await api.post(
|
||||
`/admin/stock-locations`,
|
||||
{
|
||||
name: "test location",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
stockLocationId = createResponse.data.stock_location.id
|
||||
})
|
||||
|
||||
it("should create a fulfillment set for the location", async () => {
|
||||
const response = await api.post(
|
||||
`/admin/stock-locations/${stockLocationId}/fulfillment-sets?fields=id,*fulfillment_sets`,
|
||||
{
|
||||
name: "Fulfillment Set",
|
||||
type: "shipping",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
|
||||
expect(response.data.stock_location.fulfillment_sets).toEqual([
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
// This is really just to test the new Zod middleware. We don't need more of these.
|
||||
it("should throw a validation error on wrong input", async () => {
|
||||
const errorResponse = await api
|
||||
.post(
|
||||
`/admin/stock-locations/${stockLocationId}/fulfillment-sets?fields=id,*fulfillment_sets`,
|
||||
{
|
||||
name: "Fulfillment Set",
|
||||
type: "shipping",
|
||||
foo: "bar",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
.catch((e) => e.response)
|
||||
|
||||
expect(errorResponse.status).toEqual(400)
|
||||
|
||||
expect(errorResponse.data.message).toContain("Invalid request body: ")
|
||||
})
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
@@ -84,6 +84,7 @@ module.exports = {
|
||||
[Modules.TAX]: true,
|
||||
[Modules.CURRENCY]: true,
|
||||
[Modules.PAYMENT]: true,
|
||||
[Modules.FULFILLMENT]: true,
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import {
|
||||
CreateFulfillmentSetDTO,
|
||||
IFulfillmentModuleService,
|
||||
} from "@medusajs/types"
|
||||
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||
|
||||
export const createFulfillmentSetsId = "create-fulfillment-sets"
|
||||
export const createFulfillmentSets = createStep(
|
||||
createFulfillmentSetsId,
|
||||
async (data: CreateFulfillmentSetDTO[], { container }) => {
|
||||
const service = container.resolve<IFulfillmentModuleService>(
|
||||
ModuleRegistrationName.FULFILLMENT
|
||||
)
|
||||
|
||||
const createSets = await service.create(data)
|
||||
|
||||
return new StepResponse(
|
||||
createSets,
|
||||
createSets.map((createdSet) => createdSet.id)
|
||||
)
|
||||
},
|
||||
async (createSetIds, { container }) => {
|
||||
if (!createSetIds?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const service = container.resolve<IFulfillmentModuleService>(
|
||||
ModuleRegistrationName.FULFILLMENT
|
||||
)
|
||||
|
||||
await service.delete(createSetIds)
|
||||
}
|
||||
)
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from "./add-rules-to-fulfillment-shipping-option"
|
||||
export * from "./create-fulfillment-set"
|
||||
export * from "./remove-rules-from-fulfillment-shipping-option"
|
||||
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import { ContainerRegistrationKeys } from "@medusajs/utils"
|
||||
|
||||
interface StepInput {
|
||||
input: {
|
||||
location_id: string
|
||||
fulfillment_set_ids: string[]
|
||||
}[]
|
||||
}
|
||||
|
||||
export const associateFulfillmentSetsWithLocationStepId =
|
||||
"associate-fulfillment-sets-with-location-step"
|
||||
export const associateFulfillmentSetsWithLocationStep = createStep(
|
||||
associateFulfillmentSetsWithLocationStepId,
|
||||
async (data: StepInput, { container }) => {
|
||||
if (!data.input.length) {
|
||||
return new StepResponse([], [])
|
||||
}
|
||||
|
||||
const remoteLink = container.resolve(ContainerRegistrationKeys.REMOTE_LINK)
|
||||
|
||||
const links = data.input
|
||||
.map((link) => {
|
||||
return link.fulfillment_set_ids.map((id) => {
|
||||
return {
|
||||
[Modules.FULFILLMENT]: {
|
||||
fulfillment_set_id: id,
|
||||
},
|
||||
[Modules.STOCK_LOCATION]: {
|
||||
stock_location_id: link.location_id,
|
||||
},
|
||||
}
|
||||
})
|
||||
})
|
||||
.flat()
|
||||
|
||||
const createdLinks = await remoteLink.create(links)
|
||||
|
||||
return new StepResponse(createdLinks, links)
|
||||
},
|
||||
async (links, { container }) => {
|
||||
if (!links?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const remoteLink = container.resolve(ContainerRegistrationKeys.REMOTE_LINK)
|
||||
|
||||
await remoteLink.dismiss(links)
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,33 @@
|
||||
import { CreateLocationFulfillmentSetWorkflowInputDTO } from "@medusajs/types"
|
||||
import {
|
||||
WorkflowData,
|
||||
createWorkflow,
|
||||
transform,
|
||||
} from "@medusajs/workflows-sdk"
|
||||
import { createFulfillmentSets } from "../../fulfillment"
|
||||
import { associateFulfillmentSetsWithLocationStep } from "../steps/associate-locations-with-fulfillment-sets"
|
||||
|
||||
export const createLocationFulfillmentSetWorkflowId =
|
||||
"create-location-fulfillment-set"
|
||||
export const createLocationFulfillmentSetWorkflow = createWorkflow(
|
||||
createLocationFulfillmentSetWorkflowId,
|
||||
(input: WorkflowData<CreateLocationFulfillmentSetWorkflowInputDTO>) => {
|
||||
const fulfillmentSet = createFulfillmentSets([
|
||||
{
|
||||
name: input.fulfillment_set_data.name,
|
||||
type: input.fulfillment_set_data.type,
|
||||
},
|
||||
])
|
||||
|
||||
const data = transform({ input, fulfillmentSet }, (data) => [
|
||||
{
|
||||
location_id: data.input.location_id,
|
||||
fulfillment_set_ids: [data.fulfillmentSet[0].id],
|
||||
},
|
||||
])
|
||||
|
||||
associateFulfillmentSetsWithLocationStep({
|
||||
input: data,
|
||||
})
|
||||
}
|
||||
)
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from "./create-location-fulfillment-set"
|
||||
export * from "./create-stock-locations"
|
||||
export * from "./update-stock-locations"
|
||||
export * from "./delete-stock-locations"
|
||||
export * from "./update-stock-locations"
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { createLocationFulfillmentSetWorkflow } from "@medusajs/core-flows"
|
||||
import {
|
||||
ContainerRegistrationKeys,
|
||||
remoteQueryObjectFromString,
|
||||
} from "@medusajs/utils"
|
||||
import { z } from "zod"
|
||||
import { MedusaRequest, MedusaResponse } from "../../../../../types/routing"
|
||||
import { AdminCreateStockLocationFulfillmentSet } from "../../validators"
|
||||
|
||||
export const POST = async (
|
||||
req: MedusaRequest<z.infer<typeof AdminCreateStockLocationFulfillmentSet>>,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
|
||||
|
||||
const { errors } = await createLocationFulfillmentSetWorkflow(req.scope).run({
|
||||
input: {
|
||||
location_id: req.params.id,
|
||||
fulfillment_set_data: {
|
||||
name: req.validatedBody.name,
|
||||
type: req.validatedBody.type,
|
||||
},
|
||||
},
|
||||
throwOnError: false,
|
||||
})
|
||||
|
||||
if (Array.isArray(errors) && errors[0]) {
|
||||
throw errors[0].error
|
||||
}
|
||||
|
||||
const [stock_location] = await remoteQuery(
|
||||
remoteQueryObjectFromString({
|
||||
entryPoint: "stock_locations",
|
||||
variables: {
|
||||
id: req.params.id,
|
||||
},
|
||||
fields: req.remoteQueryConfig.fields,
|
||||
})
|
||||
)
|
||||
|
||||
res.status(200).json({ stock_location })
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
import * as QueryConfig from "./query-config"
|
||||
|
||||
import { transformBody, transformQuery } from "../../../api/middlewares"
|
||||
import {
|
||||
AdminCreateStockLocationFulfillmentSet,
|
||||
AdminGetStockLocationsLocationParams,
|
||||
AdminGetStockLocationsParams,
|
||||
AdminPostStockLocationsLocationParams,
|
||||
@@ -9,11 +11,11 @@ import {
|
||||
AdminPostStockLocationsReq,
|
||||
AdminStockLocationsLocationSalesChannelBatchReq,
|
||||
} from "./validators"
|
||||
import { transformBody, transformQuery } from "../../../api/middlewares"
|
||||
|
||||
import { MiddlewareRoute } from "../../../types/middlewares"
|
||||
import { applySalesChannelsFilter } from "./utils/apply-sales-channel-filter"
|
||||
import { authenticate } from "../../../utils/authenticate-middleware"
|
||||
import { validateAndTransformBody } from "../../utils/validate-body"
|
||||
import { applySalesChannelsFilter } from "./utils/apply-sales-channel-filter"
|
||||
|
||||
export const adminStockLocationRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
{
|
||||
@@ -75,4 +77,15 @@ export const adminStockLocationRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["POST"],
|
||||
matcher: "/admin/stock-locations/:id/fulfillment-sets",
|
||||
middlewares: [
|
||||
validateAndTransformBody(AdminCreateStockLocationFulfillmentSet),
|
||||
transformQuery(
|
||||
AdminPostStockLocationsParams,
|
||||
QueryConfig.retrieveTransformQueryConfig
|
||||
),
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FindParams, extendedFindParamsMixin } from "../../../types/common"
|
||||
import { Transform, Type } from "class-transformer"
|
||||
import {
|
||||
IsNotEmpty,
|
||||
IsObject,
|
||||
@@ -6,8 +6,9 @@ import {
|
||||
IsString,
|
||||
ValidateNested,
|
||||
} from "class-validator"
|
||||
import { Transform, Type } from "class-transformer"
|
||||
import { FindParams, extendedFindParamsMixin } from "../../../types/common"
|
||||
|
||||
import { z } from "zod"
|
||||
import { IsType } from "../../../utils"
|
||||
|
||||
/**
|
||||
@@ -286,3 +287,10 @@ export class AdminStockLocationsLocationSalesChannelBatchReq {
|
||||
@IsString({ each: true })
|
||||
sales_channel_ids: string[]
|
||||
}
|
||||
|
||||
export const AdminCreateStockLocationFulfillmentSet = z
|
||||
.object({
|
||||
name: z.string(),
|
||||
type: z.string(),
|
||||
})
|
||||
.strict()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Customer, User } from "../models"
|
||||
import type { NextFunction, Request, Response } from "express"
|
||||
import type { Customer, User } from "../models"
|
||||
|
||||
import { MedusaContainer, RequestQueryFields } from "@medusajs/types"
|
||||
import { FindConfig } from "./common"
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export * from "./stock-locations"
|
||||
export * from "./fulfillment"
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { AdminFulfillmentSetResponse } from "../fulfillment"
|
||||
|
||||
export type AdminStockLocationAddressResponse = {
|
||||
id?: string
|
||||
address_1: string
|
||||
address_2?: string | null
|
||||
company?: string | null
|
||||
country_code: string
|
||||
city?: string | null
|
||||
phone?: string | null
|
||||
postal_code?: string | null
|
||||
province?: string | null
|
||||
metadata?: Record<string, unknown> | null
|
||||
created_at: string | Date
|
||||
updated_at: string | Date
|
||||
deleted_at: string | Date | null
|
||||
}
|
||||
|
||||
export interface AdminStockLocationResponse {
|
||||
id: string
|
||||
name: string
|
||||
metadata: Record<string, unknown> | null
|
||||
address_id: string
|
||||
address?: AdminStockLocationAddressResponse
|
||||
created_at: string | Date
|
||||
updated_at: string | Date
|
||||
deleted_at: string | Date | null
|
||||
|
||||
fulfillment_sets?: AdminFulfillmentSetResponse[]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./common"
|
||||
@@ -5,12 +5,15 @@ export * from "./bundles"
|
||||
export * from "./cache"
|
||||
export * from "./cart"
|
||||
export * from "./common"
|
||||
export * from "./currency"
|
||||
export * from "./customer"
|
||||
export * from "./dal"
|
||||
export * from "./event-bus"
|
||||
export * from "./feature-flag"
|
||||
export * from "./file"
|
||||
export * from "./file-service"
|
||||
export * from "./fulfillment"
|
||||
export * from "./http"
|
||||
export * from "./inventory"
|
||||
export * from "./joiner"
|
||||
export * from "./link-modules"
|
||||
@@ -34,6 +37,4 @@ export * from "./totals"
|
||||
export * from "./transaction-base"
|
||||
export * from "./user"
|
||||
export * from "./workflow"
|
||||
export * from "./currency"
|
||||
export * from "./http"
|
||||
export * from "./file"
|
||||
export * from "./workflows"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./stock-locations"
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./mutations"
|
||||
@@ -0,0 +1,7 @@
|
||||
export interface CreateLocationFulfillmentSetWorkflowInputDTO {
|
||||
location_id: string
|
||||
fulfillment_set_data: {
|
||||
name: string
|
||||
type: string
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user