feat(core-flows,medusa,types): fulfillment API: create (#7101)

what:

- adds fulfillment create API

RESOLVES CORE-1962
This commit is contained in:
Riqwan Thamir authored and GitHub committed 2024-04-23 08:35:44 +00:00
1 parent 14a7378375
commit 18f3aacee6
26 files changed
+1037 -14

No files matched your search

@@ -0,0 +1,28 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { FulfillmentTypes, IFulfillmentModuleService } from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
export const createFulfillmentStepId = "create-fulfillment"
export const createFulfillmentStep = createStep(
createFulfillmentStepId,
async (data: FulfillmentTypes.CreateFulfillmentDTO, { container }) => {
const service = container.resolve<IFulfillmentModuleService>(
ModuleRegistrationName.FULFILLMENT
)
const fulfillment = await service.createFulfillment(data)
return new StepResponse(fulfillment, fulfillment.id)
},
async (id, { container }) => {
if (!id) {
return
}
const service = container.resolve<IFulfillmentModuleService>(
ModuleRegistrationName.FULFILLMENT
)
await service.cancelFulfillment(id)
}
)
@@ -1,6 +1,7 @@
export * from "./add-rules-to-fulfillment-shipping-option"
export * from "./add-shipping-options-prices"
export * from "./cancel-fulfillment"
export * from "./create-fulfillment"
export * from "./create-fulfillment-set"
export * from "./create-service-zones"
export * from "./create-shipping-profiles"
@@ -9,4 +10,6 @@ export * from "./delete-service-zones"
export * from "./delete-shipping-options"
export * from "./remove-rules-from-fulfillment-shipping-option"
export * from "./set-shipping-options-prices"
export * from "./update-fulfillment"
export * from "./upsert-shipping-options"
export * from "./validate-shipment"
@@ -0,0 +1,35 @@
import { FulfillmentWorkflow } from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
import { updateFulfillmentWorkflow } from "../workflows/update-fulfillment"
export const updateFulfillmentWorkflowStepId = "update-fulfillment-workflow"
export const updateFulfillmentWorkflowStep = createStep(
updateFulfillmentWorkflowStepId,
async (
data: FulfillmentWorkflow.UpdateFulfillmentWorkflowInput,
{ container }
) => {
const {
transaction,
result: updated,
errors,
} = await updateFulfillmentWorkflow(container).run({
input: data,
throwOnError: false,
})
if (Array.isArray(errors) && errors[0]) {
throw errors[0].error
}
return new StepResponse(updated, transaction)
},
async (transaction, { container }) => {
if (!transaction) {
return
}
await updateFulfillmentWorkflow(container).cancel({ transaction })
}
)
@@ -0,0 +1,42 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { FulfillmentWorkflow, IFulfillmentModuleService } from "@medusajs/types"
import { getSelectsAndRelationsFromObjectArray } from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
export const updateFulfillmentStepId = "update-fulfillment"
export const updateFulfillmentStep = createStep(
updateFulfillmentStepId,
async (
input: FulfillmentWorkflow.UpdateFulfillmentWorkflowInput,
{ container }
) => {
const { id, ...data } = input
const service = container.resolve<IFulfillmentModuleService>(
ModuleRegistrationName.FULFILLMENT
)
const { selects, relations } = getSelectsAndRelationsFromObjectArray([data])
const fulfillment = await service.retrieveFulfillment(id, {
select: selects,
relations,
})
await service.updateFulfillment(id, data)
return new StepResponse(void 0, fulfillment)
},
async (fulfillment, { container }) => {
if (!fulfillment) {
return
}
const service = container.resolve<IFulfillmentModuleService>(
ModuleRegistrationName.FULFILLMENT
)
const { id, ...data } = fulfillment
// TODO: this does not revert the relationships that are created in invoke step
// There should be a consistent way to handle across workflows
await service.updateFulfillment(id, data)
}
)
@@ -0,0 +1,40 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IFulfillmentModuleService } from "@medusajs/types"
import { MedusaError } from "@medusajs/utils"
import { createStep, StepResponse } from "@medusajs/workflows-sdk"
export const validateShipmentStepId = "validate-shipment"
export const validateShipmentStep = createStep(
validateShipmentStepId,
async (id: string, { container }) => {
const service = container.resolve<IFulfillmentModuleService>(
ModuleRegistrationName.FULFILLMENT
)
const fulfillment = await service.retrieveFulfillment(id, {
select: ["shipped_at", "canceled_at", "shipping_option_id"],
})
if (fulfillment.shipped_at) {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
"Shipment has already been created"
)
}
if (fulfillment.canceled_at) {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
"Cannot create shipment for a canceled fulfillment"
)
}
if (!fulfillment.shipping_option_id) {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
"Cannot create shipment without a Shipping Option"
)
}
return new StepResponse(void 0)
}
)
@@ -0,0 +1,13 @@
import { FulfillmentDTO, FulfillmentWorkflow } from "@medusajs/types"
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import { createFulfillmentStep } from "../steps"
export const createFulfillmentWorkflowId = "create-fulfillment-workflow"
export const createFulfillmentWorkflow = createWorkflow(
createFulfillmentWorkflowId,
(
input: WorkflowData<FulfillmentWorkflow.CreateFulfillmentWorkflowInput>
): WorkflowData<FulfillmentDTO> => {
return createFulfillmentStep(input)
}
)
@@ -0,0 +1,25 @@
import { FulfillmentWorkflow } from "@medusajs/types"
import {
WorkflowData,
createWorkflow,
transform,
} from "@medusajs/workflows-sdk"
import { validateShipmentStep } from "../steps"
import { updateFulfillmentWorkflowStep } from "../steps/update-fulfillment-workflow"
export const createShipmentWorkflowId = "create-shipment-workflow"
export const createShipmentWorkflow = createWorkflow(
createShipmentWorkflowId,
(
input: WorkflowData<FulfillmentWorkflow.CreateShipmentWorkflowInput>
): WorkflowData<void> => {
validateShipmentStep(input.id)
const update = transform({ input }, (data) => ({
...data.input,
shipped_at: new Date(),
}))
updateFulfillmentWorkflowStep(update)
}
)
@@ -1,11 +1,14 @@
export * from "./add-rules-to-fulfillment-shipping-option"
export * from "./cancel-fulfillment"
export * from "./create-fulfillment"
export * from "./create-service-zones"
export * from "./create-shipment"
export * from "./create-shipping-options"
export * from "./create-shipping-profiles"
export * from "./delete-fulfillment-sets"
export * from "./delete-service-zones"
export * from "./delete-shipping-options"
export * from "./remove-rules-from-fulfillment-shipping-option"
export * from "./update-fulfillment"
export * from "./update-service-zones"
export * from "./update-shipping-options"
@@ -0,0 +1,13 @@
import { FulfillmentWorkflow } from "@medusajs/types"
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import { updateFulfillmentStep } from "../steps"
export const updateFulfillmentWorkflowId = "update-fulfillment-workflow"
export const updateFulfillmentWorkflow = createWorkflow(
updateFulfillmentWorkflowId,
(
input: WorkflowData<FulfillmentWorkflow.UpdateFulfillmentWorkflowInput>
): WorkflowData<void> => {
updateFulfillmentStep(input)
}
)
@@ -162,12 +162,12 @@ export default class Fulfillment {
@BeforeCreate()
onCreate() {
this.id = generateEntityId(this.id, "ful")
this.provider_id ??= this.provider.id
this.provider_id ??= this.provider_id ?? this.provider?.id
}
@OnInit()
onInit() {
this.id = generateEntityId(this.id, "ful")
this.provider_id ??= this.provider.id
this.provider_id ??= this.provider_id ?? this.provider?.id
}
}
@@ -0,0 +1,31 @@
import { createShipmentWorkflow } from "@medusajs/core-flows"
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../../../types/routing"
import { refetchFulfillment } from "../../helpers"
import { AdminCreateShipmentType } from "../../validators"
export const POST = async (
req: AuthenticatedMedusaRequest<AdminCreateShipmentType>,
res: MedusaResponse
) => {
const { id } = req.params
const { errors } = await createShipmentWorkflow(req.scope).run({
input: { id, ...req.validatedBody },
throwOnError: false,
})
if (Array.isArray(errors) && errors[0]) {
throw errors[0].error
}
const fulfillment = await refetchFulfillment(
id,
req.scope,
req.remoteQueryConfig.fields
)
res.status(200).json({ fulfillment })
}
@@ -3,7 +3,12 @@ import { authenticate } from "../../../utils/authenticate-middleware"
import { validateAndTransformBody } from "../../utils/validate-body"
import { validateAndTransformQuery } from "../../utils/validate-query"
import * as QueryConfig from "./query-config"
import { AdminCancelFulfillment, AdminFulfillmentParams } from "./validators"
import {
AdminCancelFulfillment,
AdminCreateFulfillment,
AdminCreateShipment,
AdminFulfillmentParams,
} from "./validators"
export const adminFulfillmentsRoutesMiddlewares: MiddlewareRoute[] = [
{
@@ -22,4 +27,26 @@ export const adminFulfillmentsRoutesMiddlewares: MiddlewareRoute[] = [
),
],
},
{
method: ["POST"],
matcher: "/admin/fulfillments",
middlewares: [
validateAndTransformBody(AdminCreateFulfillment),
validateAndTransformQuery(
AdminFulfillmentParams,
QueryConfig.retrieveTransformQueryConfig
),
],
},
{
method: ["POST"],
matcher: "/admin/fulfillments/:id/shipment",
middlewares: [
validateAndTransformBody(AdminCreateShipment),
validateAndTransformQuery(
AdminFulfillmentParams,
QueryConfig.retrieveTransformQueryConfig
),
],
},
]
@@ -9,8 +9,13 @@ export const defaultAdminFulfillmentsFields = [
"provider_id",
"shipping_option_id",
"metadata",
"order",
"created_at",
"updated_at",
"deleted_at",
"*delivery_address",
"*items",
"*labels",
]
export const retrieveTransformQueryConfig = {
@@ -0,0 +1,31 @@
import { createFulfillmentWorkflow } from "@medusajs/core-flows"
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../types/routing"
import { refetchFulfillment } from "./helpers"
import { AdminCreateFulfillmentType } from "./validators"
export const POST = async (
req: AuthenticatedMedusaRequest<AdminCreateFulfillmentType>,
res: MedusaResponse
) => {
const { result: fullfillment, errors } = await createFulfillmentWorkflow(
req.scope
).run({
input: req.validatedBody,
throwOnError: false,
})
if (Array.isArray(errors) && errors[0]) {
throw errors[0].error
}
const fulfillment = await refetchFulfillment(
fullfillment.id,
req.scope,
req.remoteQueryConfig.fields
)
res.status(200).json({ fulfillment })
}
@@ -1,7 +1,39 @@
import { z } from "zod"
import { AddressPayload } from "../../utils/common-validators"
import { createSelectParams } from "../../utils/validators"
export const AdminFulfillmentParams = createSelectParams()
export const AdminCancelFulfillment = z.object({})
const AdminCreateFulfillmentItem = z.object({
title: z.string(),
sku: z.string(),
quantity: z.number(),
barcode: z.string(),
line_item_id: z.string().optional(),
inventory_item_id: z.string().optional(),
})
const AdminCreateFulfillmentLabel = z.object({
tracking_number: z.string(),
tracking_url: z.string(),
label_url: z.string(),
})
export type AdminCancelFulfillmentType = z.infer<typeof AdminCancelFulfillment>
export const AdminCancelFulfillment = z.object({})
export type AdminCreateFulfillmentType = z.infer<typeof AdminCreateFulfillment>
export const AdminCreateFulfillment = z.object({
location_id: z.string(),
provider_id: z.string(),
delivery_address: AddressPayload,
items: z.array(AdminCreateFulfillmentItem),
labels: z.array(AdminCreateFulfillmentLabel),
order: z.object({}),
metadata: z.record(z.unknown()).optional().nullable(),
})
export type AdminCreateShipmentType = z.infer<typeof AdminCreateShipment>
export const AdminCreateShipment = z.object({
labels: z.array(AdminCreateFulfillmentLabel),
})
@@ -12,7 +12,7 @@ export const AddressPayload = z
country_code: z.string().optional(),
province: z.string().optional(),
postal_code: z.string().optional(),
metadata: z.record(z.string()).optional(),
metadata: z.record(z.string()).optional().nullable(),
})
.strict()
@@ -110,4 +110,9 @@ export interface UpdateFulfillmentDTO {
* Holds custom data in key-value pairs.
*/
metadata?: Record<string, unknown> | null
/**
* The labels associated with the fulfillment.
*/
labels?: Omit<CreateFulfillmentLabelDTO, "fulfillment_id">[]
}
@@ -0,0 +1,183 @@
/**
* The fulfillment address to be created.
*/
export type CreateFulfillmentAddressWorkflowDTO = {
/**
* The company of the fulfillment address.
*/
company?: string | null
/**
* The first name of the fulfillment address.
*/
first_name?: string | null
/**
* The last name of the fulfillment address.
*/
last_name?: string | null
/**
* The first line of the fulfillment address.
*/
address_1?: string | null
/**
* The second line of the fulfillment address.
*/
address_2?: string | null
/**
* The city of the fulfillment address.
*/
city?: string | null
/**
* The ISO 2 character country code of the fulfillment address.
*/
country_code?: string | null
/**
* The province of the fulfillment address.
*/
province?: string | null
/**
* The postal code of the fulfillment address.
*/
postal_code?: string | null
/**
* The phone of the fulfillment address.
*/
phone?: string | null
/**
* Holds custom data in key-value pairs.
*/
metadata?: Record<string, unknown> | null
}
/**
* The fulfillment item to be created.
*/
export type CreateFulfillmentItemWorkflowDTO = {
/**
* The title of the fulfillment item.
*/
title: string
/**
* The SKU of the fulfillment item.
*/
sku: string
/**
* The quantity of the fulfillment item.
*/
quantity: number
/**
* The barcode of the fulfillment item.
*/
barcode: string
/**
* The associated line item's ID.
*/
line_item_id?: string | null
/**
* The associated inventory item's ID.
*/
inventory_item_id?: string | null
}
/**
* The fulfillment label to be created.
*/
export type CreateFulfillmentLabelWorkflowDTO = {
/**
* The tracking number of the fulfillment label.
*/
tracking_number: string
/**
* The tracking URL of the fulfillment label.
*/
tracking_url: string
/**
* The URL of the label.
*/
label_url: string
}
export type CreateFulfillmentOrderWorkflowDTO = Record<string, any>
export type CreateFulfillmentWorkflowInput = {
/**
* The associated location's ID.
*/
location_id: string
/**
* The date the fulfillment was packed.
*/
packed_at?: Date | null
/**
* The date the fulfillment was shipped.
*/
shipped_at?: Date | null
/**
* The date the fulfillment was delivered.
*/
delivered_at?: Date | null
/**
* The date the fulfillment was canceled.
*/
canceled_at?: Date | null
/**
* The data necessary for the associated fulfillment provider to process the fulfillment.
*/
data?: Record<string, unknown> | null
/**
* The associated fulfillment provider's ID.
*/
provider_id: string
/**
* The associated shipping option's ID.
*/
shipping_option_id?: string | null
/**
* Holds custom data in key-value pairs.
*/
metadata?: Record<string, unknown> | null
/**
* The address associated with the fulfillment. It's used for delivery.
*/
delivery_address: CreateFulfillmentAddressWorkflowDTO
/**
* The items associated with the fulfillment.
*/
items: CreateFulfillmentItemWorkflowDTO[]
/**
* The labels associated with the fulfillment.
*/
labels: CreateFulfillmentLabelWorkflowDTO[]
/**
* The associated fulfillment order.
*/
order: CreateFulfillmentOrderWorkflowDTO
}
@@ -0,0 +1,16 @@
import { CreateFulfillmentLabelWorkflowDTO } from "./create-fulfillment"
/**
* The attributes to update in the fulfillment.
*/
export interface CreateShipmentWorkflowInput {
/**
* The ID of the fulfillment
*/
id: string
/**
* The labels associated with the fulfillment.
*/
labels?: CreateFulfillmentLabelWorkflowDTO[]
}
@@ -1,5 +1,8 @@
export * from "./create-fulfillment"
export * from "./create-shipment"
export * from "./create-shipping-options"
export * from "./service-zones"
export * from "./delete-shipping-options"
export * from "./service-zones"
export * from "./shipping-profiles"
export * from "./update-fulfillment"
export * from "./update-shipping-options"
@@ -0,0 +1,39 @@
/**
* The attributes to update in the fulfillment.
*/
export interface UpdateFulfillmentWorkflowInput {
/**
* The ID of the fulfillment
*/
id: string
/**
* The associated location's ID.
*/
location_id?: string
/**
* The date the fulfillment was packed.
*/
packed_at?: Date | null
/**
* The date the fulfillment was shipped.
*/
shipped_at?: Date | null
/**
* The date the fulfillment was delivered.
*/
delivered_at?: Date | null
/**
* The data necessary for the associated fulfillment provider to process the fulfillment.
*/
data?: Record<string, unknown> | null
/**
* Holds custom data in key-value pairs.
*/
metadata?: Record<string, unknown> | null
}