feat(core-flows,dashboard,types,fulfillment,medusa): uses requires shipping throughout lifecycle (#9170)
what: - uses requires shipping throughout lifecycle https://github.com/user-attachments/assets/d5ba89d3-5ea0-49c4-b2d5-490c4764933e
This commit is contained in:
1 parent
e54b339324
commit
69f6645716
22 files changed
+490
-108
No files matched your search
@@ -1091,6 +1091,7 @@
|
||||
"statusTitle": "Fulfillment Status",
|
||||
"fulfillItems": "Fulfill items",
|
||||
"awaitingFulfillmentBadge": "Awaiting fulfillment",
|
||||
"requiresShipping": "Requires shipping",
|
||||
"number": "Fulfillment #{{number}}",
|
||||
"itemsToFulfill": "Items to fulfill",
|
||||
"create": "Create Fulfillment",
|
||||
|
||||
@@ -2,14 +2,14 @@ import { AdminOrderLineItem } from "@medusajs/types"
|
||||
|
||||
export function getReturnableQuantity(item: AdminOrderLineItem): number {
|
||||
const {
|
||||
shipped_quantity,
|
||||
delivered_quantity,
|
||||
return_received_quantity,
|
||||
return_dismissed_quantity,
|
||||
return_requested_quantity,
|
||||
} = item.detail
|
||||
|
||||
return (
|
||||
shipped_quantity -
|
||||
delivered_quantity -
|
||||
(return_received_quantity +
|
||||
return_requested_quantity +
|
||||
return_dismissed_quantity)
|
||||
|
||||
+33
-15
@@ -8,6 +8,7 @@ import { Alert, Button, Select, Switch, toast } from "@medusajs/ui"
|
||||
import { useForm, useWatch } from "react-hook-form"
|
||||
|
||||
import { OrderLineItemDTO } from "@medusajs/types"
|
||||
import { useSearchParams } from "react-router-dom"
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
import {
|
||||
RouteFocusModal,
|
||||
@@ -16,40 +17,48 @@ import {
|
||||
import { useCreateOrderFulfillment } from "../../../../../hooks/api/orders"
|
||||
import { useStockLocations } from "../../../../../hooks/api/stock-locations"
|
||||
import { getFulfillableQuantity } from "../../../../../lib/order-item"
|
||||
import { OrderCreateFulfillmentItem } from "./order-create-fulfillment-item"
|
||||
import { CreateFulfillmentSchema } from "./constants"
|
||||
import { useShippingOptions } from "../../../../../hooks/api"
|
||||
import { OrderCreateFulfillmentItem } from "./order-create-fulfillment-item"
|
||||
|
||||
type OrderCreateFulfillmentFormProps = {
|
||||
order: AdminOrder
|
||||
requiresShipping: boolean
|
||||
}
|
||||
|
||||
export function OrderCreateFulfillmentForm({
|
||||
order,
|
||||
requiresShipping,
|
||||
}: OrderCreateFulfillmentFormProps) {
|
||||
const { t } = useTranslation()
|
||||
const { handleSuccess } = useRouteModal()
|
||||
const [searchParams] = useSearchParams()
|
||||
|
||||
const { mutateAsync: createOrderFulfillment, isPending: isMutating } =
|
||||
useCreateOrderFulfillment(order.id)
|
||||
|
||||
const [fulfillableItems, setFulfillableItems] = useState(() =>
|
||||
(order.items || []).filter((item) => getFulfillableQuantity(item) > 0)
|
||||
(order.items || []).filter(
|
||||
(item) =>
|
||||
item.requires_shipping === requiresShipping &&
|
||||
getFulfillableQuantity(item) > 0
|
||||
)
|
||||
)
|
||||
|
||||
const form = useForm<zod.infer<typeof CreateFulfillmentSchema>>({
|
||||
defaultValues: {
|
||||
quantity: fulfillableItems.reduce((acc, item) => {
|
||||
acc[item.id] = getFulfillableQuantity(item)
|
||||
return acc
|
||||
}, {} as Record<string, number>),
|
||||
quantity: fulfillableItems.reduce(
|
||||
(acc, item) => {
|
||||
acc[item.id] = getFulfillableQuantity(item)
|
||||
return acc
|
||||
},
|
||||
{} as Record<string, number>
|
||||
),
|
||||
send_notification: !order.no_notification,
|
||||
},
|
||||
resolver: zodResolver(CreateFulfillmentSchema),
|
||||
})
|
||||
|
||||
const { stock_locations = [] } = useStockLocations()
|
||||
const { shipping_options = [] } = useShippingOptions()
|
||||
|
||||
const handleSubmit = form.handleSubmit(async (data) => {
|
||||
try {
|
||||
@@ -84,12 +93,18 @@ export function OrderCreateFulfillmentForm({
|
||||
})
|
||||
|
||||
const fulfilledQuantityArray = (order.items || []).map(
|
||||
(item) => item.detail.fulfilled_quantity
|
||||
(item) =>
|
||||
item.requires_shipping === requiresShipping &&
|
||||
item.detail.fulfilled_quantity
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const itemsToFulfill =
|
||||
order?.items?.filter((item) => getFulfillableQuantity(item) > 0) || []
|
||||
order?.items?.filter(
|
||||
(item) =>
|
||||
item.requires_shipping === requiresShipping &&
|
||||
getFulfillableQuantity(item) > 0
|
||||
) || []
|
||||
|
||||
setFulfillableItems(itemsToFulfill)
|
||||
|
||||
@@ -102,13 +117,16 @@ export function OrderCreateFulfillmentForm({
|
||||
})
|
||||
}
|
||||
|
||||
const quantityMap = itemsToFulfill.reduce((acc, item) => {
|
||||
acc[item.id] = getFulfillableQuantity(item as OrderLineItemDTO)
|
||||
return acc
|
||||
}, {} as Record<string, number>)
|
||||
const quantityMap = itemsToFulfill.reduce(
|
||||
(acc, item) => {
|
||||
acc[item.id] = getFulfillableQuantity(item as OrderLineItemDTO)
|
||||
return acc
|
||||
},
|
||||
{} as Record<string, number>
|
||||
)
|
||||
|
||||
form.setValue("quantity", quantityMap)
|
||||
}, [...fulfilledQuantityArray])
|
||||
}, [...fulfilledQuantityArray, requiresShipping])
|
||||
|
||||
return (
|
||||
<RouteFocusModal.Form form={form}>
|
||||
|
||||
+9
-2
@@ -1,4 +1,4 @@
|
||||
import { useParams } from "react-router-dom"
|
||||
import { useParams, useSearchParams } from "react-router-dom"
|
||||
|
||||
import { RouteFocusModal } from "../../../components/modals"
|
||||
import { useOrder } from "../../../hooks/api/orders"
|
||||
@@ -6,6 +6,8 @@ import { OrderCreateFulfillmentForm } from "./components/order-create-fulfillmen
|
||||
|
||||
export function OrderCreateFulfillment() {
|
||||
const { id } = useParams()
|
||||
const [searchParams] = useSearchParams()
|
||||
const requiresShipping = searchParams.get("requires_shipping") === "true"
|
||||
|
||||
const { order, isLoading, isError, error } = useOrder(id!, {
|
||||
fields: "currency_code,*items,*items.variant,*shipping_address",
|
||||
@@ -19,7 +21,12 @@ export function OrderCreateFulfillment() {
|
||||
|
||||
return (
|
||||
<RouteFocusModal>
|
||||
{ready && <OrderCreateFulfillmentForm order={order} />}
|
||||
{ready && (
|
||||
<OrderCreateFulfillmentForm
|
||||
order={order}
|
||||
requiresShipping={requiresShipping}
|
||||
/>
|
||||
)}
|
||||
</RouteFocusModal>
|
||||
)
|
||||
}
|
||||
+54
-11
@@ -2,6 +2,7 @@ import { Buildings, XCircle } from "@medusajs/icons"
|
||||
import {
|
||||
AdminOrder,
|
||||
AdminOrderFulfillment,
|
||||
AdminOrderLineItem,
|
||||
HttpTypes,
|
||||
OrderLineItemDTO,
|
||||
} from "@medusajs/types"
|
||||
@@ -108,25 +109,64 @@ const UnfulfilledItem = ({
|
||||
}
|
||||
|
||||
const UnfulfilledItemBreakdown = ({ order }: { order: AdminOrder }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
// Create an array of order items that haven't been fulfilled or at least not fully fulfilled
|
||||
const unfulfilledItems = order.items!.filter(
|
||||
(i) => i.detail.fulfilled_quantity < i.quantity
|
||||
const unfulfilledItemsWithShipping = order.items!.filter(
|
||||
(i) => i.requires_shipping && i.detail.fulfilled_quantity < i.quantity
|
||||
)
|
||||
|
||||
if (!unfulfilledItems.length) {
|
||||
return null
|
||||
}
|
||||
const unfulfilledItemsWithoutShipping = order.items!.filter(
|
||||
(i) => !i.requires_shipping && i.detail.fulfilled_quantity < i.quantity
|
||||
)
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
{!!unfulfilledItemsWithShipping.length && (
|
||||
<UnfulfilledItemDisplay
|
||||
order={order}
|
||||
unfulfilledItems={unfulfilledItemsWithShipping}
|
||||
requiresShipping={true}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!!unfulfilledItemsWithoutShipping.length && (
|
||||
<UnfulfilledItemDisplay
|
||||
order={order}
|
||||
unfulfilledItems={unfulfilledItemsWithoutShipping}
|
||||
requiresShipping={false}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const UnfulfilledItemDisplay = ({
|
||||
order,
|
||||
unfulfilledItems,
|
||||
requiresShipping = false,
|
||||
}: {
|
||||
order: AdminOrder
|
||||
unfulfilledItems: AdminOrderLineItem[]
|
||||
requiresShipping: boolean
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Container className="divide-y p-0">
|
||||
<div className="flex items-center justify-between px-6 py-4">
|
||||
<Heading level="h2">{t("orders.fulfillment.unfulfilledItems")}</Heading>
|
||||
|
||||
<div className="flex items-center gap-x-4">
|
||||
{requiresShipping && (
|
||||
<StatusBadge color="red" className="text-nowrap">
|
||||
{t("orders.fulfillment.requiresShipping")}
|
||||
</StatusBadge>
|
||||
)}
|
||||
|
||||
<StatusBadge color="red" className="text-nowrap">
|
||||
{t("orders.fulfillment.awaitingFulfillmentBadge")}
|
||||
</StatusBadge>
|
||||
|
||||
<ActionMenu
|
||||
groups={[
|
||||
{
|
||||
@@ -134,7 +174,7 @@ const UnfulfilledItemBreakdown = ({ order }: { order: AdminOrder }) => {
|
||||
{
|
||||
label: t("orders.fulfillment.fulfillItems"),
|
||||
icon: <Buildings />,
|
||||
to: `/orders/${order.id}/fulfillment`,
|
||||
to: `/orders/${order.id}/fulfillment?requires_shipping=${requiresShipping}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -143,7 +183,7 @@ const UnfulfilledItemBreakdown = ({ order }: { order: AdminOrder }) => {
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{unfulfilledItems.map((item) => (
|
||||
{unfulfilledItems.map((item: AdminOrderLineItem) => (
|
||||
<UnfulfilledItem
|
||||
key={item.id}
|
||||
item={item}
|
||||
@@ -178,7 +218,9 @@ const Fulfillment = ({
|
||||
}
|
||||
)
|
||||
|
||||
let statusText = "Awaiting shipping"
|
||||
let statusText = fulfillment.requires_shipping
|
||||
? "Awaiting shipping"
|
||||
: "Awaiting delivery"
|
||||
let statusColor: "blue" | "green" | "red" = "blue"
|
||||
let statusTimestamp = fulfillment.created_at
|
||||
|
||||
@@ -205,7 +247,8 @@ const Fulfillment = ({
|
||||
const showShippingButton =
|
||||
!fulfillment.canceled_at &&
|
||||
!fulfillment.shipped_at &&
|
||||
!fulfillment.delivered_at
|
||||
!fulfillment.delivered_at &&
|
||||
fulfillment.requires_shipping
|
||||
const showDeliveryButton =
|
||||
!fulfillment.canceled_at && !fulfillment.delivered_at
|
||||
|
||||
|
||||
Reference in new issue
Block a user