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:
Riqwan Thamir authored and GitHub committed 2024-09-24 08:26:22 +00:00
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 -2
View File
@@ -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)
@@ -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}>
@@ -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>
)
}
@@ -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