fix(dashboard): handle deleted products/variants in the order domain (#9841)

* fix: optional variants and products

* fix: more cases with inventory

* fix: fulfillment creation modal

* fix: move create fulfillment actions to footer

* refactor: use properties of the LineItem

* fix: remove N/A

* fix: remove ||

* fix: show confirmed status properly

* fix: pick needed product props

* fix: typo
This commit is contained in:
Frane Polić
2024-11-12 09:05:11 +01:00
committed by GitHub
parent 713e428ec2
commit 81208b6e1d
23 changed files with 111 additions and 94 deletions

View File

@@ -4,12 +4,12 @@ import { Thumbnail } from "../../../../common/thumbnail"
import { HttpTypes } from "@medusajs/types"
type ProductCellProps = {
product: HttpTypes.AdminProduct
product: Pick<HttpTypes.AdminProduct, "thumbnail" | "title">
}
export const ProductCell = ({ product }: ProductCellProps) => {
return (
<div className="flex h-full w-full items-center gap-x-3 overflow-hidden max-w-[250px]">
<div className="flex h-full w-full max-w-[250px] items-center gap-x-3 overflow-hidden">
<div className="w-fit flex-shrink-0">
<Thumbnail src={product.thumbnail} />
</div>

View File

@@ -38,8 +38,8 @@ export function OrderAllocateItemsForm({ order }: OrderAllocateItemsFormProps) {
() =>
order.items.filter(
(item) =>
item.variant.manage_inventory &&
item.variant.inventory.length &&
item.variant?.manage_inventory &&
item.variant?.inventory.length &&
item.quantity - item.detail.fulfilled_quantity > 0
),
[order.items]
@@ -48,8 +48,8 @@ export function OrderAllocateItemsForm({ order }: OrderAllocateItemsFormProps) {
const filteredItems = useMemo(() => {
return itemsToAllocate.filter(
(i) =>
i.variant.title.toLowerCase().includes(filterTerm) ||
i.variant.product.title.toLowerCase().includes(filterTerm)
i.variant_title.toLowerCase().includes(filterTerm) ||
i.product_title.toLowerCase().includes(filterTerm)
)
}, [itemsToAllocate, filterTerm])
@@ -151,9 +151,9 @@ export function OrderAllocateItemsForm({ order }: OrderAllocateItemsFormProps) {
const item = itemsToAllocate.find((i) => i.id === lineItem.id)
item.variant.inventory_items.forEach((ii, ind) => {
item.variant?.inventory_items.forEach((ii, ind) => {
const num = value || 0
const inventory = item.variant.inventory[ind]
const inventory = item.variant?.inventory[ind]
form.setValue(
`quantity.${lineItem.id}-${inventory.id}`,
@@ -161,7 +161,7 @@ export function OrderAllocateItemsForm({ order }: OrderAllocateItemsFormProps) {
)
if (value) {
const location = inventory.location_levels.find(
const location = inventory?.location_levels.find(
(l) => l.location_id === selectedLocationId
)
if (location) {
@@ -313,16 +313,16 @@ function defaultAllocations(items: OrderLineItemDTO) {
const ret = {}
items.forEach((item) => {
const hasInventoryKit = item.variant.inventory_items.length > 1
const hasInventoryKit = item.variant?.inventory_items.length > 1
ret[
hasInventoryKit
? `${item.id}-`
: `${item.id}-${item.variant.inventory[0].id}`
: `${item.id}-${item.variant?.inventory[0].id}`
] = ""
if (hasInventoryKit) {
item.variant.inventory.forEach((i) => {
item.variant?.inventory.forEach((i) => {
ret[`${item.id}-${i.id}`] = ""
})
}

View File

@@ -37,7 +37,7 @@ export function OrderAllocateItemsItem({
const { t } = useTranslation()
const variant = item.variant
const inventory = item.variant.inventory
const inventory = item.variant?.inventory || []
const [isOpen, setIsOpen] = useState(false)
@@ -54,8 +54,6 @@ export function OrderAllocateItemsItem({
return {}
}
const { inventory } = variant
const locationInventory = inventory[0]?.location_levels?.find(
(inv) => inv.location_id === locationId
)
@@ -73,8 +71,8 @@ export function OrderAllocateItemsItem({
const hasQuantityError =
!hasInventoryKit &&
availableQuantity &&
quantityField[`${item.id}-${item.variant.inventory[0].id}`] &&
quantityField[`${item.id}-${item.variant.inventory[0].id}`] >
quantityField[`${item.id}-${item.variant?.inventory[0].id}`] &&
quantityField[`${item.id}-${item.variant?.inventory[0].id}`] >
availableQuantity
const minValue = 0
@@ -95,12 +93,12 @@ export function OrderAllocateItemsItem({
<div className="flex flex-col">
<div className="flex flex-row">
<Text className="txt-small flex" as="span" weight="plus">
{item.variant.product.title}
{item.product_title}
</Text>
{item.variant.sku && (
{item.variant_sku && (
<span className="text-ui-fg-subtle">
{" "}
({item.variant.sku})
({item.variant_sku})
</span>
)}
{hasInventoryKit && (
@@ -134,13 +132,13 @@ export function OrderAllocateItemsItem({
{availableQuantity &&
!hasInventoryKit &&
quantityField[
`${item.id}-${item.variant.inventory[0].id}`
`${item.id}-${item.variant?.inventory[0].id}`
] && (
<span className="text-ui-fg-error txt-small ml-1">
-
{
quantityField[
`${item.id}-${item.variant.inventory[0].id}`
`${item.id}-${item.variant?.inventory[0].id}`
]
}
</span>
@@ -173,7 +171,7 @@ export function OrderAllocateItemsItem({
name={
hasInventoryKit
? `quantity.${item.id}-`
: `quantity.${item.id}-${item.variant.inventory[0].id}`
: `quantity.${item.id}-${item.variant?.inventory[0].id}`
}
rules={{
required: !hasInventoryKit,
@@ -196,7 +194,7 @@ export function OrderAllocateItemsItem({
: Number(e.target.value)
onQuantityChange(
item.variant.inventory[0],
item.variant?.inventory[0],
item,
hasInventoryKit,
val,

View File

@@ -70,9 +70,9 @@ export const AddClaimItemsTable = ({
if (q) {
results = results.filter((i) => {
return (
i.variant.product.title.toLowerCase().includes(q.toLowerCase()) ||
i.variant.title.toLowerCase().includes(q.toLowerCase()) ||
i.variant.sku?.toLowerCase().includes(q.toLowerCase())
i.product_title.toLowerCase().includes(q.toLowerCase()) ||
i.variant_title.toLowerCase().includes(q.toLowerCase()) ||
i.variant_sku?.toLowerCase().includes(q.toLowerCase())
)
})
}
@@ -173,14 +173,14 @@ const sortItems = (
let bValue: any
if (field === "product_title") {
aValue = a.variant.product.title
bValue = b.variant.product.title
aValue = a.product_title
bValue = b.product_title
} else if (field === "variant_title") {
aValue = a.variant.title
bValue = b.variant.title
aValue = a.variant_title
bValue = b.variant_title
} else if (field === "sku") {
aValue = a.variant.sku
bValue = b.variant.sku
aValue = a.variant_sku
bValue = b.variant_sku
} else if (field === "returnable_quantity") {
aValue = a.quantity - (a.returned_quantity || 0)
bValue = b.quantity - (b.returned_quantity || 0)

View File

@@ -52,7 +52,12 @@ export const useClaimItemTableColumns = (currencyCode: string) => {
id: "product",
header: () => <ProductHeader />,
cell: ({ row }) => (
<ProductCell product={row.original.variant.product} />
<ProductCell
product={{
thumbnail: row.original.thumbnail,
title: row.original.product_title,
}}
/>
),
}),
columnHelper.accessor("variant.sku", {

View File

@@ -482,7 +482,7 @@ export const ClaimCreateForm = ({
return true
}
if (!item.variant.manage_inventory) {
if (!item.variant?.manage_inventory) {
return true
}

View File

@@ -51,10 +51,10 @@ function ClaimInboundItem({
{item.title}{" "}
</Text>
{item.variant?.sku && <span>({item.variant.sku})</span>}
{item.variant_sku && <span>({item.variant_sku})</span>}
</div>
<Text as="div" className="text-ui-fg-subtle txt-small">
{item.variant?.product?.title}
{item.product_title}
</Text>
</div>
</div>

View File

@@ -235,7 +235,7 @@ export const ClaimOutboundSection = ({
return true
}
if (!item.variant.manage_inventory) {
if (!item.variant?.manage_inventory) {
return true
}

View File

@@ -56,9 +56,9 @@ export const AddExchangeInboundItemsTable = ({
if (q) {
results = results.filter((i) => {
return (
i.variant.product.title.toLowerCase().includes(q.toLowerCase()) ||
i.variant.title.toLowerCase().includes(q.toLowerCase()) ||
i.variant.sku?.toLowerCase().includes(q.toLowerCase())
i.product_title.toLowerCase().includes(q.toLowerCase()) ||
i.variant_title.toLowerCase().includes(q.toLowerCase()) ||
i.variant_sku?.toLowerCase().includes(q.toLowerCase())
)
})
}
@@ -133,14 +133,14 @@ const sortItems = (
let bValue: any
if (field === "product_title") {
aValue = a.variant.product.title
bValue = b.variant.product.title
aValue = a.product_title
bValue = b.product_title
} else if (field === "variant_title") {
aValue = a.variant.title
bValue = b.variant.title
aValue = a.variant_title
bValue = b.variant_title
} else if (field === "sku") {
aValue = a.variant.sku
bValue = b.variant.sku
aValue = a.variant_sku
bValue = b.variant_sku
}
if (aValue < bValue) {

View File

@@ -52,7 +52,12 @@ export const useExchangeItemTableColumns = (currencyCode: string) => {
id: "product",
header: () => <ProductHeader />,
cell: ({ row }) => (
<ProductCell product={row.original.variant.product} />
<ProductCell
product={{
thumbnail: row.original.thumbnail,
title: row.original.product_title,
}}
/>
),
}),
columnHelper.accessor("variant.sku", {

View File

@@ -51,10 +51,10 @@ function ExchangeInboundItem({
{item.title}{" "}
</Text>
{item.variant?.sku && <span>({item.variant.sku})</span>}
{item.variant_sku && <span>({item.variant_sku})</span>}
</div>
<Text as="div" className="text-ui-fg-subtle txt-small">
{item.variant?.product?.title}
{item.product_title}
</Text>
</div>
</div>

View File

@@ -287,7 +287,7 @@ export const ExchangeInboundSection = ({
return true
}
if (!item.variant.manage_inventory) {
if (!item.variant?.manage_inventory) {
return true
}
@@ -486,7 +486,7 @@ export const ExchangeInboundSection = ({
<Text
size="small"
leading="compact"
className="text-ui-fg-muted inline ml-1"
className="text-ui-fg-muted ml-1 inline"
>
({t("fields.optional")})
</Text>

View File

@@ -244,7 +244,7 @@ export const ExchangeOutboundSection = ({
return true
}
if (!item.variant.manage_inventory) {
if (!item.variant?.manage_inventory) {
return true
}

View File

@@ -138,18 +138,7 @@ export function OrderCreateFulfillmentForm({
onSubmit={handleSubmit}
className="flex h-full flex-col overflow-hidden"
>
<RouteFocusModal.Header>
<div className="flex items-center justify-end gap-x-2">
<RouteFocusModal.Close asChild>
<Button size="small" variant="secondary">
{t("actions.cancel")}
</Button>
</RouteFocusModal.Close>
<Button size="small" type="submit" isLoading={isMutating}>
{t("orders.fulfillment.create")}
</Button>
</div>
</RouteFocusModal.Header>
<RouteFocusModal.Header />
<RouteFocusModal.Body className="flex h-full w-full flex-col items-center divide-y overflow-y-auto">
<div className="flex size-full flex-col items-center overflow-auto p-16">
@@ -309,6 +298,18 @@ export function OrderCreateFulfillmentForm({
</div>
</div>
</RouteFocusModal.Body>
<RouteFocusModal.Footer>
<div className="flex items-center justify-end gap-x-2">
<RouteFocusModal.Close asChild>
<Button size="small" variant="secondary">
{t("actions.cancel")}
</Button>
</RouteFocusModal.Close>
<Button size="small" type="submit" isLoading={isMutating}>
{t("orders.fulfillment.create")}
</Button>
</div>
</RouteFocusModal.Footer>
</KeyboundForm>
</RouteFocusModal.Form>
)

View File

@@ -10,7 +10,6 @@ import { Thumbnail } from "../../../../../components/common/thumbnail/index"
import { useProductVariant } from "../../../../../hooks/api/products"
import { getFulfillableQuantity } from "../../../../../lib/order-item"
import { CreateFulfillmentSchema } from "./constants"
import { useReservationItems } from "../../../../../hooks/api/reservations"
type OrderEditItemProps = {
item: HttpTypes.AdminOrderLineItem
@@ -30,10 +29,13 @@ export function OrderCreateFulfillmentItem({
const { t } = useTranslation()
const { variant } = useProductVariant(
item.variant!.product_id!,
item.variant_id!,
item.product_id,
item.variant_id,
{
fields: "*inventory,*inventory.location_levels",
},
{
enabled: !!item.variant,
}
)
@@ -77,10 +79,10 @@ export function OrderCreateFulfillmentItem({
<Text className="txt-small" as="span" weight="plus">
{item.title}
</Text>
{item.variant?.sku && <span>({item.variant.sku})</span>}
{item.variant_sku && <span>({item.variant_sku})</span>}
</div>
<Text as="div" className="text-ui-fg-subtle txt-small">
{item.variant?.title ?? ""}
{item.variant_title}
</Text>
</div>
</div>

View File

@@ -71,9 +71,9 @@ export const AddReturnItemsTable = ({
if (q) {
results = results.filter((i) => {
return (
i.variant.product.title.toLowerCase().includes(q.toLowerCase()) ||
i.variant.title.toLowerCase().includes(q.toLowerCase()) ||
i.variant.sku?.toLowerCase().includes(q.toLowerCase())
i.product_title.toLowerCase().includes(q.toLowerCase()) ||
i.variant_title.toLowerCase().includes(q.toLowerCase()) ||
i.variant_sku?.toLowerCase().includes(q.toLowerCase())
)
})
}
@@ -174,14 +174,14 @@ const sortItems = (
let bValue: any
if (field === "product_title") {
aValue = a.variant.product.title
bValue = b.variant.product.title
aValue = a.product_title
bValue = b.product_title
} else if (field === "variant_title") {
aValue = a.variant.title
bValue = b.variant.title
aValue = a.variant_title
bValue = b.variant_title
} else if (field === "sku") {
aValue = a.variant.sku
bValue = b.variant.sku
aValue = a.variant_sku
bValue = b.variant_sku
} else if (field === "returnable_quantity") {
aValue = a.quantity - (a.returned_quantity || 0)
bValue = b.quantity - (b.returned_quantity || 0)

View File

@@ -52,7 +52,12 @@ export const useReturnItemTableColumns = (currencyCode: string) => {
id: "product",
header: () => <ProductHeader />,
cell: ({ row }) => (
<ProductCell product={row.original.variant.product} />
<ProductCell
product={{
thumbnail: row.original.thumbnail,
title: row.original.product_title,
}}
/>
),
}),
columnHelper.accessor("variant.sku", {

View File

@@ -325,7 +325,7 @@ export const ReturnCreateForm = ({
return true
}
if (!item.variant.manage_inventory) {
if (!item.variant?.manage_inventory) {
return true
}
@@ -355,7 +355,7 @@ export const ReturnCreateForm = ({
return undefined
}
return await sdk.admin.product.retrieveVariant(
item.variant.product.id,
item.product_id,
item.variant_id,
{ fields: "*inventory,*inventory.location_levels" }
)

View File

@@ -53,10 +53,10 @@ function ReturnItem({
<Text className="txt-small" as="span" weight="plus">
{item.title}{" "}
</Text>
{item.variant.sku && <span>({item.variant.sku})</span>}
{item.variant_sku && <span>({item.variant_sku})</span>}
</div>
<Text as="div" className="text-ui-fg-subtle txt-small">
{item.variant.product.title}
{item.product_title}
</Text>
</div>
</div>

View File

@@ -369,6 +369,8 @@ const useActivityItems = (order: AdminOrder): Activity[] => {
timestamp:
edit.status === "requested"
? edit.requested_at
: edit.status === "confirmed"
? edit.confirmed_at
: edit.status === "declined"
? edit.declined_at
: edit.status === "canceled"

View File

@@ -73,14 +73,14 @@ const UnfulfilledItem = ({
>
{item.title}
</Text>
{item.variant.sku && (
{item.variant_sku && (
<div className="flex items-center gap-x-1">
<Text size="small">{item.variant.sku}</Text>
<Copy content={item.variant.sku} className="text-ui-fg-muted" />
<Text size="small">{item.variant_sku}</Text>
<Copy content={item.variant_sku} className="text-ui-fg-muted" />
</div>
)}
<Text size="small">
{item.variant.options.map((o) => o.value).join(" · ")}
{item.variant?.options.map((o) => o.value).join(" · ")}
</Text>
</div>
</div>
@@ -118,7 +118,6 @@ const UnfulfilledItemBreakdown = ({ order }: { order: AdminOrder }) => {
(i) => !i.requires_shipping && i.detail.fulfilled_quantity < i.quantity
)
return (
<>
{!!unfulfilledItemsWithShipping.length && (
@@ -428,7 +427,7 @@ const Fulfillment = ({
</div>
{(showShippingButton || showDeliveryButton) && (
<div className="bg-ui-bg-subtle flex items-center justify-end rounded-b-xl px-4 py-4 gap-x-2">
<div className="bg-ui-bg-subtle flex items-center justify-end gap-x-2 rounded-b-xl px-4 py-4">
{showDeliveryButton && (
<Button onClick={handleMarkAsDelivered} variant="secondary">
{t("orders.fulfillment.markAsDelivered")}

View File

@@ -746,7 +746,7 @@ const InventoryKitBreakdown = ({ item }: { item: AdminOrderLineItem }) => {
const [isOpen, setIsOpen] = useState(false)
const inventory = item.variant.inventory_items
const inventory = item.variant?.inventory_items || []
return (
<>

View File

@@ -250,12 +250,12 @@ export function OrderReceiveReturnForm({
<Text className="txt-small" as="span" weight="plus">
{item.title}{" "}
</Text>
{originalItem.variant.sku && (
<span>({originalItem.variant.sku})</span>
{originalItem.variant_sku && (
<span>({originalItem.variant_sku})</span>
)}
</div>
<Text as="div" className="text-ui-fg-subtle txt-small">
{originalItem.variant.product.title}
{originalItem.product_title}
</Text>
</div>
</div>