Skip to content

Commit

Permalink
feat(dashboard): order fulfillment UI (medusajs#7262)
Browse files Browse the repository at this point in the history
* feat: initial impl. of Unfulfilled section and create flow

* feat: create fulfillment

* feat: order <> fulfillment link, fulfillment section

* feat: accept order_id when creating fulfillment

* feat: finish create and cancel

* fix: integration test

* refactor: real Order<>Fulfillment link instead readonly, add link step to the workflow

* fix: revert `order_id` definitions

* chore: add changeset

* fix: build

* fix: address comments

* fix: fetch inventory and location levels for fulfilled variant

* fix: loading inventory details

* add isList to order fulfillment link

* fix: duplicate declaration

* fix: type

* refactor: link orders step, fix client

* fix: move translations to the new file

* fix: pass order id in test

---------

Co-authored-by: olivermrbl <[email protected]>
  • Loading branch information
fPolic and olivermrbl authored May 20, 2024
1 parent c9bffdf commit 521b4e7
Show file tree
Hide file tree
Showing 29 changed files with 713 additions and 49 deletions.
9 changes: 9 additions & 0 deletions .changeset/long-islands-press.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@medusajs/link-modules": patch
"@medusajs/core-flows": patch
"@medusajs/types": patch
"@medusajs/utils": patch
"@medusajs/medusa": patch
---

feat: add Order<>Fulfillment link
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export function generateCreateFulfillmentData(
data: Partial<CreateFulfillmentDTO> & {
provider_id: string
shipping_option_id: string
order_id: string
}
) {
const randomString = Math.random().toString(36).substring(7)
Expand Down Expand Up @@ -49,6 +50,7 @@ export function generateCreateFulfillmentData(
},
],
order: data.order ?? {},
order_id: data.order_id,
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ medusaIntegrationTestRunner({
const data = generateCreateFulfillmentData({
provider_id: providerId,
shipping_option_id: shippingOption.id,
order_id: "fake-order",
})
const { errors } = await workflow.run({
input: data,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ medusaIntegrationTestRunner({
const data = generateCreateFulfillmentData({
provider_id: providerId,
shipping_option_id: shippingOption.id,
order_id: "order_123",
})

const response = await api
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -529,8 +529,20 @@
"unfulfilledItems": "Unfulfilled Items",
"statusLabel": "Fulfillment status",
"statusTitle": "Fulfillment Status",
"awaitingFullfillmentBadge": "Awaiting fulfillment",
"fulfillItems": "Fulfill items",
"awaitingFulfillmentBadge": "Awaiting fulfillment",
"number": "Fulfillment #{{number}}",
"itemsToFulfill": "Items to fulfill",
"create": "Create Fulfillment",
"available": "Available",
"inStock": "In stock",
"itemsToFulfillDesc": "Choose items and quantities to fulfill",
"locationDescription": "Choose which location you want to fulfill items from.",
"error": {
"wrongQuantity": "Only one item is available for fulfillment",
"wrongQuantity_other": "Quantity should be a number between 1 and {{number}}",
"noItems": "No items to fulfill."
},
"status": {
"notFulfilled": "Not fulfilled",
"partiallyFulfilled": "Partially fulfilled",
Expand All @@ -543,6 +555,7 @@
"requiresAction": "Requires action"
},
"toast": {
"created": "Fulfillment created successfully",
"canceled": "Fulfillment successfully canceled",
"fulfillmentShipped": "Cannot cancel an already shipped fulfillment"
},
Expand Down
43 changes: 43 additions & 0 deletions packages/admin-next/dashboard/src/hooks/api/fulfillment.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { useMutation, UseMutationOptions } from "@tanstack/react-query"

import { queryKeysFactory } from "../../lib/query-key-factory"

import { client } from "../../lib/client"
import { queryClient } from "../../lib/medusa"
import { ordersQueryKeys } from "./orders"

const FULFILLMENTS_QUERY_KEY = "fulfillments" as const
export const fulfillmentsQueryKeys = queryKeysFactory(FULFILLMENTS_QUERY_KEY)

export const useCreateFulfillment = (
options?: UseMutationOptions<any, Error, any>
) => {
return useMutation({
mutationFn: (payload: any) => client.fulfillments.create(payload),
onSuccess: (data: any, variables: any, context: any) => {
queryClient.invalidateQueries({ queryKey: fulfillmentsQueryKeys.lists() })
queryClient.invalidateQueries({
queryKey: ordersQueryKeys.details(),
})
options?.onSuccess?.(data, variables, context)
},
...options,
})
}

export const useCancelFulfillment = (
id: string,
options?: UseMutationOptions<any, Error, any>
) => {
return useMutation({
mutationFn: () => client.fulfillments.cancel(id),
onSuccess: (data: any, variables: any, context: any) => {
queryClient.invalidateQueries({ queryKey: fulfillmentsQueryKeys.lists() })
queryClient.invalidateQueries({
queryKey: ordersQueryKeys.details(),
})
options?.onSuccess?.(data, variables, context)
},
...options,
})
}
14 changes: 14 additions & 0 deletions packages/admin-next/dashboard/src/i18n/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -536,8 +536,21 @@
"unfulfilledItems": "Unfulfilled Items",
"statusLabel": "Fulfillment status",
"statusTitle": "Fulfillment Status",
"fulfillItems": "Fulfill items",
"awaitingFulfillmentBadge": "Awaiting fulfillment",
"awaitingFullfillmentBadge": "Awaiting fulfillment",
"number": "Fulfillment #{{number}}",
"itemsToFulfill": "Items to fulfill",
"create": "Create Fulfillment",
"available": "Available",
"inStock": "In stock",
"itemsToFulfillDesc": "Choose items and quantities to fulfill",
"locationDescription": "Choose which location you want to fulfill items from.",
"error": {
"wrongQuantity": "Only one item is available for fulfillment",
"wrongQuantity_other": "Quantity should be a number between 1 and {{number}}",
"noItems": "No items to fulfill."
},
"status": {
"notFulfilled": "Not fulfilled",
"partiallyFulfilled": "Partially fulfilled",
Expand All @@ -550,6 +563,7 @@
"requiresAction": "Requires action"
},
"toast": {
"created": "Fulfillment created successfully",
"canceled": "Fulfillment successfully canceled",
"fulfillmentShipped": "Cannot cancel an already shipped fulfillment"
},
Expand Down
4 changes: 3 additions & 1 deletion packages/admin-next/dashboard/src/lib/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ import { customers } from "./customers"
import { fulfillmentProviders } from "./fulfillment-providers"
import { inventoryItems } from "./inventory"
import { invites } from "./invites"
import { orders } from "./orders"
import { payments } from "./payments"
import { priceLists } from "./price-lists"
import { productTypes } from "./product-types"
import { products } from "./products"
import { promotions } from "./promotions"
import { regions } from "./regions"
import { orders } from "./orders"
import { fulfillments } from "./fulfillments"
import { reservations } from "./reservations"
import { salesChannels } from "./sales-channels"
import { shippingOptions } from "./shipping-options"
Expand Down Expand Up @@ -49,6 +50,7 @@ export const client = {
invites: invites,
inventoryItems: inventoryItems,
reservations: reservations,
fulfillments: fulfillments,
fulfillmentProviders: fulfillmentProviders,
products: products,
productTypes: productTypes,
Expand Down
17 changes: 17 additions & 0 deletions packages/admin-next/dashboard/src/lib/client/fulfillments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { CreateFulfillmentDTO } from "@medusajs/types"

import { FulfillmentRes } from "../../types/api-responses"
import { postRequest } from "./common"

async function createFulfillment(payload: CreateFulfillmentDTO) {
return postRequest<FulfillmentRes>(`/admin/fulfillments`, payload)
}

async function cancelFulfillment(id: string) {
return postRequest<FulfillmentRes>(`/admin/fulfillments/${id}/cancel`)
}

export const fulfillments = {
create: createFulfillment,
cancel: cancelFulfillment,
}
16 changes: 16 additions & 0 deletions packages/admin-next/dashboard/src/lib/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,19 @@ export function pick(obj: Record<string, any>, keys: string[]) {

return ret
}

/**
* Remove properties that are `null` or `undefined` from the object.
* @param obj
*/
export function cleanNonValues(obj: Record<string, any>) {
const ret: Record<string, any> = {}

for (const key in obj) {
if (obj[key] !== null && typeof obj[key] !== "undefined") {
ret[key] = obj[key]
}
}

return ret
}
5 changes: 5 additions & 0 deletions packages/admin-next/dashboard/src/lib/order-item.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { OrderLineItemDTO } from "@medusajs/types"

export const getFulfillableQuantity = (item: OrderLineItemDTO) => {
return item.quantity - item.detail.fulfilled_quantity
}
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,13 @@ export const RouteMap: RouteObject[] = [
{
path: ":id",
lazy: () => import("../../v2-routes/orders/order-detail"),
children: [
{
path: "fulfillment",
lazy: () =>
import("../../v2-routes/orders/order-create-fulfillment"),
},
],
},
],
},
Expand Down
6 changes: 6 additions & 0 deletions packages/admin-next/dashboard/src/types/api-responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
CampaignDTO,
CurrencyDTO,
CustomerGroupDTO,
FulfillmentDTO,
FulfillmentProviderDTO,
InventoryNext,
InviteDTO,
Expand Down Expand Up @@ -73,6 +74,11 @@ export type RegionRes = { region: RegionDTO }
export type RegionListRes = { regions: RegionDTO[] } & ListRes
export type RegionDeleteRes = DeleteRes

// Fulfillments
export type FulfillmentRes = { fulfillment: FulfillmentDTO }
export type FulfillmentListRes = { fulfillments: FulfillmentDTO[] } & ListRes
export type FulfillmentDeleteRes = DeleteRes

// Reservations
export type ReservationRes = { reservation: InventoryNext.ReservationItemDTO }
export type ReservationListRes = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const inventoryDetailQuery = (id: string) => ({
queryKey: inventoryItemsQueryKeys.detail(id),
queryFn: async () =>
client.inventoryItems.retrieve(id, {
fields: "*variants",
fields: "*variant",
}),
})

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { z } from "zod"

export const CreateFulfillmentSchema = z.object({
quantity: z.record(z.string(), z.number()),

location_id: z.string(),
send_notification: z.boolean().optional(),
})
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./order-create-fulfillment-form"
Loading

0 comments on commit 521b4e7

Please sign in to comment.