Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(medusa,fulfillment): pass stock location data to fulfillment provider #9322

Merged
merged 8 commits into from
Sep 28, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ export function generateCreateFulfillmentData(
provider_id: string
shipping_option_id: string
order_id: string
location_id: string
}
) {
const randomString = Math.random().toString(36).substring(7)

return {
location_id: "test-location",
location_id: data.location_id,
packed_at: null,
shipped_at: null,
delivered_at: null,
Expand Down Expand Up @@ -97,8 +98,10 @@ export async function setupFullDataFulfillmentStructure(
service: IFulfillmentModuleService,
{
providerId,
locationId,
}: {
providerId: string
locationId: string
}
) {
const randomString = Math.random().toString(36).substring(7)
Expand Down Expand Up @@ -133,6 +136,8 @@ export async function setupFullDataFulfillmentStructure(

await service.createFulfillment(
generateCreateFulfillmentData({
order_id: "fake-order",
location_id: locationId,
provider_id: providerId,
shipping_option_id: shippingOption.id,
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@ import {
updateFulfillmentWorkflow,
updateFulfillmentWorkflowId,
} from "@medusajs/core-flows"
import { IFulfillmentModuleService } from "@medusajs/types"
import { Modules } from "@medusajs/utils"
import {
IFulfillmentModuleService,
MedusaContainer,
StockLocationDTO,
} from "@medusajs/types"
import { ContainerRegistrationKeys, Modules } from "@medusajs/utils"
import { medusaIntegrationTestRunner } from "medusa-test-utils"
import {
generateCreateFulfillmentData,
Expand All @@ -22,15 +26,110 @@ medusaIntegrationTestRunner({
env: { MEDUSA_FF_MEDUSA_V2: true },
testSuite: ({ getContainer }) => {
describe("Workflows: Fulfillment", () => {
let appContainer
let location: StockLocationDTO
let appContainer: MedusaContainer
let service: IFulfillmentModuleService

beforeAll(async () => {
appContainer = getContainer()
service = appContainer.resolve(Modules.FULFILLMENT)
})

beforeEach(async () => {
const stockLocationService = appContainer.resolve(
Modules.STOCK_LOCATION
)

location = await stockLocationService.createStockLocations({
name: "Test Location",
address: {
address_1: "Test Address",
address_2: "tttest",
city: "Test City",
country_code: "us",
postal_code: "12345",
metadata: { email: "[email protected]" },
},
metadata: { custom_location: "yes" },
})
})

describe("createFulfillmentWorkflow", () => {
describe("invoke", () => {
it("should get stock location", async () => {
const workflow = createFulfillmentWorkflow(appContainer)

const link = appContainer.resolve(
ContainerRegistrationKeys.REMOTE_LINK
)

const shippingProfile = await service.createShippingProfiles({
name: "test",
type: "default",
})

const fulfillmentSet = await service.createFulfillmentSets({
name: "test",
type: "test-type",
})

await link.create({
[Modules.STOCK_LOCATION]: {
stock_location_id: location.id,
},
[Modules.FULFILLMENT]: {
fulfillment_set_id: fulfillmentSet.id,
},
})

const serviceZone = await service.createServiceZones({
name: "test",
fulfillment_set_id: fulfillmentSet.id,
})

const shippingOption = await service.createShippingOptions(
generateCreateShippingOptionsData({
provider_id: providerId,
service_zone_id: serviceZone.id,
shipping_profile_id: shippingProfile.id,
})
)

const data = generateCreateFulfillmentData({
provider_id: providerId,
shipping_option_id: shippingOption.id,
order_id: "fake-order",
location_id: location.id,
})

const { transaction } = await workflow.run({
input: data,
throwOnError: true,
})

expect(
transaction.context.invoke["get-location"].output.output
).toEqual({
id: expect.any(String),
created_at: expect.any(Date),
updated_at: expect.any(Date),
name: "Test Location",
address: {
id: expect.any(String),
address_1: "Test Address",
address_2: "tttest",
city: "Test City",
country_code: "us",
postal_code: "12345",
metadata: { email: "[email protected]" },
phone: null,
province: null,
},
metadata: { custom_location: "yes" },
})
})
})

describe("compensation", () => {
it("should cancel created fulfillment if step following step throws error", async () => {
const workflow = createFulfillmentWorkflow(appContainer)
Expand Down Expand Up @@ -70,6 +169,7 @@ medusaIntegrationTestRunner({
provider_id: providerId,
shipping_option_id: shippingOption.id,
order_id: "fake-order",
location_id: location.id,
})
const { errors } = await workflow.run({
input: data,
Expand Down Expand Up @@ -130,19 +230,24 @@ medusaIntegrationTestRunner({
)

const data = generateCreateFulfillmentData({
order_id: "fake-order",
provider_id: providerId,
shipping_option_id: shippingOption.id,
location_id: location.id,
})

const fulfillment = await service.createFulfillment(data)
const fulfillment = await service.createFulfillment({
...data,
location,
})

const date = new Date()
const { errors } = await workflow.run({
input: {
id: fulfillment.id,
shipped_at: date,
packed_at: date,
location_id: "new location",
location_id: location.id,
},
throwOnError: false,
})
Expand Down Expand Up @@ -209,12 +314,15 @@ medusaIntegrationTestRunner({
)

const data = generateCreateFulfillmentData({
order_id: "fake-order",
provider_id: providerId,
shipping_option_id: shippingOption.id,
location_id: location.id,
})

const fulfillment = await service.createFulfillment({
...data,
location,
labels: [],
})

Expand Down
40 changes: 34 additions & 6 deletions integration-tests/modules/__tests__/fulfillment/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IFulfillmentModuleService } from "@medusajs/types"
import { IFulfillmentModuleService, StockLocationDTO } from "@medusajs/types"
import { Modules } from "@medusajs/utils"
import { medusaIntegrationTestRunner } from "medusa-test-utils"
import { createAdminUser } from "../../../helpers/create-admin-user"
Expand All @@ -21,6 +21,7 @@ medusaIntegrationTestRunner({
testSuite: ({ getContainer, api, dbConnection }) => {
let service: IFulfillmentModuleService
let container
let location: StockLocationDTO

beforeAll(() => {
container = getContainer()
Expand All @@ -29,6 +30,20 @@ medusaIntegrationTestRunner({

beforeEach(async () => {
await createAdminUser(dbConnection, adminHeaders, container)
const stockLocationService = container.resolve(Modules.STOCK_LOCATION)

location = await stockLocationService.createStockLocations({
name: "Test Location",
address: {
address_1: "Test Address",
address_2: "tttest",
city: "Test City",
country_code: "us",
postal_code: "12345",
metadata: { email: "[email protected]" },
},
metadata: { custom_location: "yes" },
})
})

/**
Expand All @@ -38,7 +53,10 @@ medusaIntegrationTestRunner({
*/
describe("Fulfillment module migrations backward compatibility", () => {
it("should allow to create a full data structure after the backward compatible migration have run on top of the medusa v1 database", async () => {
await setupFullDataFulfillmentStructure(service, { providerId })
await setupFullDataFulfillmentStructure(service, {
providerId,
locationId: location.id,
})

const fulfillmentSets = await service.listFulfillmentSets(
{},
Expand Down Expand Up @@ -92,7 +110,10 @@ medusaIntegrationTestRunner({
})

it("should cancel a fulfillment", async () => {
await setupFullDataFulfillmentStructure(service, { providerId })
await setupFullDataFulfillmentStructure(service, {
providerId,
locationId: location.id,
})

const [fulfillment] = await service.listFulfillments()

Expand Down Expand Up @@ -138,6 +159,7 @@ medusaIntegrationTestRunner({
)

const data = generateCreateFulfillmentData({
location_id: location.id,
provider_id: providerId,
shipping_option_id: shippingOption.id,
order_id: "order_123",
Expand All @@ -151,7 +173,7 @@ medusaIntegrationTestRunner({
expect(response.data.fulfillment).toEqual(
expect.objectContaining({
id: expect.any(String),
location_id: "test-location",
location_id: location.id,
packed_at: null,
shipped_at: null,
delivered_at: null,
Expand Down Expand Up @@ -218,7 +240,10 @@ medusaIntegrationTestRunner({
})

it("should update a fulfillment to be shipped", async () => {
await setupFullDataFulfillmentStructure(service, { providerId })
await setupFullDataFulfillmentStructure(service, {
providerId,
locationId: location.id,
})

const [fulfillment] = await service.listFulfillments()

Expand Down Expand Up @@ -255,7 +280,10 @@ medusaIntegrationTestRunner({
})

it("should throw error when already shipped", async () => {
await setupFullDataFulfillmentStructure(service, { providerId })
await setupFullDataFulfillmentStructure(service, {
providerId,
locationId: location.id,
})

const [fulfillment] = await service.listFulfillments()

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { FulfillmentDTO, FulfillmentWorkflow } from "@medusajs/framework/types"
import {
FulfillmentDTO,
FulfillmentWorkflow,
StockLocationDTO,
} from "@medusajs/framework/types"
import {
WorkflowData,
WorkflowResponse,
createWorkflow,
transform,
} from "@medusajs/framework/workflows-sdk"
import { createFulfillmentStep } from "../steps"
import { useRemoteQueryStep } from "../../common"

export const createFulfillmentWorkflowId = "create-fulfillment-workflow"
/**
Expand All @@ -15,6 +21,47 @@ export const createFulfillmentWorkflow = createWorkflow(
(
input: WorkflowData<FulfillmentWorkflow.CreateFulfillmentWorkflowInput>
): WorkflowResponse<FulfillmentDTO> => {
return new WorkflowResponse(createFulfillmentStep(input))
const location: StockLocationDTO = useRemoteQueryStep({
entry_point: "stock_location",
fields: [
"id",
"name",
"metadata",
"created_at",
"updated_at",
"address.id",
"address.address_1",
"address.address_2",
"address.city",
"address.country_code",
"address.phone",
"address.province",
"address.postal_code",
"address.metadata",
],
variables: { id: input.location_id },
list: false,
throw_if_key_not_found: true,
}).config({ name: "get-location" })

const stepInput = transform({ input, location }, ({ input, location }) => {
return {
...input,
location,
}
})

// When we have support for hooks with a return this would be a great
// place to put a hook for people to collect additional data they would
// like to pass down to the provider.
//
// const providerDataHook = createHook("getProviderData", stepInput)
//
// The collected provider data would be passed to createFulfillment in a
// additional_provider_data: Record<string, unknown> field.

const result = createFulfillmentStep(stepInput)

return new WorkflowResponse(result)
}
)
Loading
Loading