feat: Add create location fulfillment set flow + API (#6945)

This commit is contained in:
Oli Juhl
2024-04-05 19:27:38 +02:00
committed by GitHub
parent 01487970b2
commit 626aa09497
18 changed files with 291 additions and 10 deletions
@@ -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: ")
})
})
},
})
+1
View File
@@ -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"
@@ -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 -1
View File
@@ -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
View File
@@ -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"
+4 -3
View File
@@ -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"
+1
View File
@@ -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
}
}