feat(core-flows,dashboard): adds item validations for claims, returns and exchanges (#8735)

what:

- adds item validations for claims, returns and exchanges
- prevents autofocus on item add
- reset item quantity when error
This commit is contained in:
Riqwan Thamir authored and GitHub committed 2024-08-23 09:30:27 +00:00
1 parent b23f0f8188
commit 59599ac237
9 files changed
+217 -54

No files matched your search

@@ -427,6 +427,30 @@ medusaIntegrationTestRunner({
}, },
adminHeaders adminHeaders
) )
const { response } = await api
.post(`/admin/exchanges/${exchangeId2}/request`, {}, adminHeaders)
.catch((e) => e)
expect(response.data).toEqual({
type: "invalid_data",
message:
"Order exchange request should have atleast 1 item inbound and 1 item outbound",
})
await api.post(
`/admin/exchanges/${exchangeId2}/outbound/items`,
{
items: [
{
variant_id: productExtra.variants[0].id,
quantity: 2,
},
],
},
adminHeaders
)
await api.post( await api.post(
`/admin/exchanges/${exchangeId2}/request`, `/admin/exchanges/${exchangeId2}/request`,
{}, {},
@@ -36,7 +36,7 @@ import { AddClaimItemsTable } from "../add-claim-items-table"
import { ClaimInboundItem } from "./claim-inbound-item.tsx" import { ClaimInboundItem } from "./claim-inbound-item.tsx"
import { ClaimCreateSchema, CreateClaimSchemaType } from "./schema" import { ClaimCreateSchema, CreateClaimSchemaType } from "./schema"
import { AdminReturn } from "@medusajs/types" import { AdminReturn, HttpTypes } from "@medusajs/types"
import { import {
useAddClaimInboundItems, useAddClaimInboundItems,
useAddClaimInboundShipping, useAddClaimInboundShipping,
@@ -275,7 +275,10 @@ export const ClaimCreateForm = ({
}) })
} }
} else { } else {
append({ item_id: i.id, quantity: i.detail.return_requested_quantity }) append(
{ item_id: i.id, quantity: i.detail.return_requested_quantity },
{ shouldFocus: false }
)
} }
}) })
@@ -551,16 +554,26 @@ export const ClaimCreateForm = ({
}) })
} }
}} }}
onUpdate={(payload) => { onUpdate={(payload: HttpTypes.AdminUpdateReturnItems) => {
const actionId = previewItems const action = previewItems
.find((i) => i.id === item.item_id) .find((i) => i.id === item.item_id)
?.actions?.find((a) => a.action === "RETURN_ITEM")?.id ?.actions?.find((a) => a.action === "RETURN_ITEM")
if (actionId) { if (action) {
updateInboundItem( updateInboundItem(
{ ...payload, actionId }, { ...payload, actionId: action.id },
{ {
onError: (error) => { onError: (error) => {
if (
action.details?.quantity &&
payload.quantity
) {
form.setValue(
`inbound_items.${index}.quantity`,
action.details?.quantity as number
)
}
toast.error(error.message) toast.error(error.message)
}, },
} }
@@ -9,6 +9,7 @@ import { useEffect, useMemo, useState } from "react"
import { useFieldArray, UseFormReturn } from "react-hook-form" import { useFieldArray, UseFormReturn } from "react-hook-form"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import { HttpTypes } from "@medusajs/types"
import { Form } from "../../../../../components/common/form" import { Form } from "../../../../../components/common/form"
import { Combobox } from "../../../../../components/inputs/combobox" import { Combobox } from "../../../../../components/inputs/combobox"
import { import {
@@ -134,11 +135,14 @@ export const ClaimOutboundSection = ({
}) })
} }
} else { } else {
append({ append(
item_id: i.id, {
quantity: i.detail.quantity, item_id: i.id,
variant_id: i.variant_id, quantity: i.detail.quantity,
}) variant_id: i.variant_id,
},
{ shouldFocus: false }
)
} }
}) })
@@ -349,7 +353,7 @@ export const ClaimOutboundSection = ({
}) })
} }
}} }}
onUpdate={(payload) => { onUpdate={(payload: HttpTypes.AdminUpdateReturnItems) => {
const actionId = previewOutboundItems const actionId = previewOutboundItems
.find((i) => i.id === item.item_id) .find((i) => i.id === item.item_id)
?.actions?.find((a) => a.action === "ITEM_ADD")?.id ?.actions?.find((a) => a.action === "ITEM_ADD")?.id
@@ -10,6 +10,7 @@ import { useEffect, useMemo, useState } from "react"
import { useFieldArray, UseFormReturn } from "react-hook-form" import { useFieldArray, UseFormReturn } from "react-hook-form"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import { HttpTypes } from "@medusajs/types"
import { Form } from "../../../../../components/common/form" import { Form } from "../../../../../components/common/form"
import { Combobox } from "../../../../../components/inputs/combobox" import { Combobox } from "../../../../../components/inputs/combobox"
import { import {
@@ -172,7 +173,10 @@ export const ExchangeInboundSection = ({
}) })
} }
} else { } else {
append({ item_id: i.id, quantity: i.detail.return_requested_quantity }) append(
{ item_id: i.id, quantity: i.detail.return_requested_quantity },
{ shouldFocus: false }
)
} }
}) })
@@ -409,16 +413,23 @@ export const ExchangeInboundSection = ({
}) })
} }
}} }}
onUpdate={(payload) => { onUpdate={(payload: HttpTypes.AdminUpdateReturnItems) => {
const actionId = previewInboundItems const action = previewInboundItems
.find((i) => i.id === item.item_id) .find((i) => i.id === item.item_id)
?.actions?.find((a) => a.action === "RETURN_ITEM")?.id ?.actions?.find((a) => a.action === "RETURN_ITEM")
if (actionId) { if (action) {
updateInboundItem( updateInboundItem(
{ ...payload, actionId }, { ...payload, actionId: action.id },
{ {
onError: (error) => { onError: (error) => {
if (action.details?.quantity && payload.quantity) {
form.setValue(
`inbound_items.${index}.quantity`,
action.details?.quantity as number
)
}
toast.error(error.message) toast.error(error.message)
}, },
} }
@@ -134,11 +134,14 @@ export const ExchangeOutboundSection = ({
}) })
} }
} else { } else {
append({ append(
item_id: i.id, {
quantity: i.detail.quantity, item_id: i.id,
variant_id: i.variant_id, quantity: i.detail.quantity,
}) variant_id: i.variant_id,
},
{ shouldFocus: false }
)
} }
}) })
@@ -1,5 +1,6 @@
import React, { useEffect, useMemo, useState } from "react"
import { zodResolver } from "@hookform/resolvers/zod" import { zodResolver } from "@hookform/resolvers/zod"
import { PencilSquare } from "@medusajs/icons"
import { AdminOrder, InventoryLevelDTO, ReturnDTO } from "@medusajs/types"
import { import {
Alert, Alert,
Button, Button,
@@ -10,10 +11,9 @@ import {
Text, Text,
toast, toast,
} from "@medusajs/ui" } from "@medusajs/ui"
import { useEffect, useMemo, useState } from "react"
import { useFieldArray, useForm } from "react-hook-form" import { useFieldArray, useForm } from "react-hook-form"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import { AdminOrder, InventoryLevelDTO, ReturnDTO } from "@medusajs/types"
import { PencilSquare } from "@medusajs/icons"
import { import {
RouteFocusModal, RouteFocusModal,
@@ -22,14 +22,8 @@ import {
useStackedModal, useStackedModal,
} from "../../../../../components/modals" } from "../../../../../components/modals"
import { ReturnCreateSchema, ReturnCreateSchemaType } from "./schema"
import { AddReturnItemsTable } from "../add-return-items-table"
import { Form } from "../../../../../components/common/form" import { Form } from "../../../../../components/common/form"
import { ReturnItem } from "./return-item"
import { Combobox } from "../../../../../components/inputs/combobox" import { Combobox } from "../../../../../components/inputs/combobox"
import { useStockLocations } from "../../../../../hooks/api/stock-locations"
import { useShippingOptions } from "../../../../../hooks/api/shipping-options"
import { getStylizedAmount } from "../../../../../lib/money-amount-helpers"
import { import {
useAddReturnItem, useAddReturnItem,
useAddReturnShipping, useAddReturnShipping,
@@ -41,8 +35,14 @@ import {
useUpdateReturnItem, useUpdateReturnItem,
useUpdateReturnShipping, useUpdateReturnShipping,
} from "../../../../../hooks/api/returns" } from "../../../../../hooks/api/returns"
import { currencies } from "../../../../../lib/data/currencies" import { useShippingOptions } from "../../../../../hooks/api/shipping-options"
import { useStockLocations } from "../../../../../hooks/api/stock-locations"
import { sdk } from "../../../../../lib/client" import { sdk } from "../../../../../lib/client"
import { currencies } from "../../../../../lib/data/currencies"
import { getStylizedAmount } from "../../../../../lib/money-amount-helpers"
import { AddReturnItemsTable } from "../add-return-items-table"
import { ReturnItem } from "./return-item"
import { ReturnCreateSchema, ReturnCreateSchemaType } from "./schema"
type ReturnCreateFormProps = { type ReturnCreateFormProps = {
order: AdminOrder order: AdminOrder
@@ -455,12 +455,26 @@ export const ReturnCreateForm = ({
} }
}} }}
onUpdate={(payload) => { onUpdate={(payload) => {
const actionId = previewItems const action = previewItems
.find((i) => i.id === item.item_id) .find((i) => i.id === item.item_id)
?.actions?.find((a) => a.action === "RETURN_ITEM")?.id ?.actions?.find((a) => a.action === "RETURN_ITEM")
if (actionId) { if (action) {
updateReturnItem({ ...payload, actionId }) updateReturnItem(
{ ...payload, actionId: action.id },
{
onError: (error) => {
if (action.details?.quantity && payload.quantity) {
form.setValue(
`items.${index}.quantity`,
action.details?.quantity as number
)
}
toast.error(error.message)
},
}
)
} }
}} }}
index={index} index={index}
@@ -3,26 +3,29 @@ import {
OrderChangeActionDTO, OrderChangeActionDTO,
OrderChangeDTO, OrderChangeDTO,
OrderClaimDTO, OrderClaimDTO,
OrderClaimItemDTO,
OrderDTO, OrderDTO,
OrderPreviewDTO, OrderPreviewDTO,
OrderReturnItemDTO,
} from "@medusajs/types" } from "@medusajs/types"
import { import {
ChangeActionType, ChangeActionType,
MedusaError,
Modules, Modules,
OrderChangeStatus, OrderChangeStatus,
ReturnStatus, ReturnStatus,
} from "@medusajs/utils" } from "@medusajs/utils"
import { import {
WorkflowResponse,
createStep, createStep,
createWorkflow, createWorkflow,
parallelize, parallelize,
transform, transform,
when, when,
WorkflowResponse,
} from "@medusajs/workflows-sdk" } from "@medusajs/workflows-sdk"
import { createRemoteLinkStep, useRemoteQueryStep } from "../../../common"
import { reserveInventoryStep } from "../../../cart/steps/reserve-inventory" import { reserveInventoryStep } from "../../../cart/steps/reserve-inventory"
import { prepareConfirmInventoryInput } from "../../../cart/utils/prepare-confirm-inventory-input" import { prepareConfirmInventoryInput } from "../../../cart/utils/prepare-confirm-inventory-input"
import { createRemoteLinkStep, useRemoteQueryStep } from "../../../common"
import { createReturnFulfillmentWorkflow } from "../../../fulfillment/workflows/create-return-fulfillment" import { createReturnFulfillmentWorkflow } from "../../../fulfillment/workflows/create-return-fulfillment"
import { previewOrderChangeStep, updateReturnsStep } from "../../steps" import { previewOrderChangeStep, updateReturnsStep } from "../../steps"
import { createOrderClaimItemsFromActionsStep } from "../../steps/claim/create-claim-items-from-actions" import { createOrderClaimItemsFromActionsStep } from "../../steps/claim/create-claim-items-from-actions"
@@ -58,6 +61,29 @@ export const confirmClaimRequestValidationStep = createStep(
} }
) )
/**
* This step confirms that a requested claim has atleast one item to return or send
*/
const confirmIfClaimItemsArePresent = createStep(
"confirm-if-items-are-present",
async function ({
claimItems,
returnItems,
}: {
claimItems: OrderClaimItemDTO[]
returnItems?: OrderReturnItemDTO[]
}) {
if (claimItems.length > 0 || (returnItems && returnItems.length > 0)) {
return
}
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Order claim request should have atleast 1 item`
)
}
)
function prepareFulfillmentData({ function prepareFulfillmentData({
order, order,
items, items,
@@ -188,7 +214,13 @@ export const confirmClaimRequestWorkflow = createWorkflow(
): WorkflowResponse<OrderPreviewDTO> { ): WorkflowResponse<OrderPreviewDTO> {
const orderClaim: OrderClaimDTO = useRemoteQueryStep({ const orderClaim: OrderClaimDTO = useRemoteQueryStep({
entry_point: "order_claim", entry_point: "order_claim",
fields: ["id", "status", "order_id", "canceled_at"], fields: [
"id",
"status",
"order_id",
"canceled_at",
"additional_items.id",
],
variables: { id: input.claim_id }, variables: { id: input.claim_id },
list: false, list: false,
throw_if_key_not_found: true, throw_if_key_not_found: true,
@@ -241,12 +273,16 @@ export const confirmClaimRequestWorkflow = createWorkflow(
const orderPreview = previewOrderChangeStep(order.id) const orderPreview = previewOrderChangeStep(order.id)
const [createClaimItems, createdReturnItems] = parallelize( const [createdClaimItems, createdReturnItems] = parallelize(
createOrderClaimItemsFromActionsStep(claimItems), createOrderClaimItemsFromActionsStep(claimItems),
createReturnItemsFromActionsStep(returnItems), createReturnItemsFromActionsStep(returnItems)
confirmOrderChanges({ changes: [orderChange], orderId: order.id })
) )
confirmIfClaimItemsArePresent({
claimItems: createdClaimItems,
returnItems: createdReturnItems,
})
const returnId = transform( const returnId = transform(
{ createdReturnItems }, { createdReturnItems },
({ createdReturnItems }) => { ({ createdReturnItems }) => {
@@ -254,6 +290,8 @@ export const confirmClaimRequestWorkflow = createWorkflow(
} }
) )
confirmOrderChanges({ changes: [orderChange], orderId: order.id })
when({ returnId }, ({ returnId }) => { when({ returnId }, ({ returnId }) => {
return !!returnId return !!returnId
}).then(() => { }).then(() => {
@@ -266,9 +304,12 @@ export const confirmClaimRequestWorkflow = createWorkflow(
]) ])
}) })
const claimId = transform({ createClaimItems }, ({ createClaimItems }) => { const claimId = transform(
return createClaimItems?.[0]?.claim_id { createdClaimItems },
}) ({ createdClaimItems }) => {
return createdClaimItems?.[0]?.claim_id
}
)
const { returnShippingMethod, claimShippingMethod } = transform( const { returnShippingMethod, claimShippingMethod } = transform(
{ orderPreview, orderClaim, returnId }, { orderPreview, orderClaim, returnId },
@@ -4,10 +4,13 @@ import {
OrderChangeDTO, OrderChangeDTO,
OrderDTO, OrderDTO,
OrderExchangeDTO, OrderExchangeDTO,
OrderExchangeItemDTO,
OrderPreviewDTO, OrderPreviewDTO,
OrderReturnItemDTO,
} from "@medusajs/types" } from "@medusajs/types"
import { import {
ChangeActionType, ChangeActionType,
MedusaError,
Modules, Modules,
OrderChangeStatus, OrderChangeStatus,
ReturnStatus, ReturnStatus,
@@ -20,9 +23,9 @@ import {
transform, transform,
when, when,
} from "@medusajs/workflows-sdk" } from "@medusajs/workflows-sdk"
import { createRemoteLinkStep, useRemoteQueryStep } from "../../../common"
import { reserveInventoryStep } from "../../../cart/steps/reserve-inventory" import { reserveInventoryStep } from "../../../cart/steps/reserve-inventory"
import { prepareConfirmInventoryInput } from "../../../cart/utils/prepare-confirm-inventory-input" import { prepareConfirmInventoryInput } from "../../../cart/utils/prepare-confirm-inventory-input"
import { createRemoteLinkStep, useRemoteQueryStep } from "../../../common"
import { createReturnFulfillmentWorkflow } from "../../../fulfillment/workflows/create-return-fulfillment" import { createReturnFulfillmentWorkflow } from "../../../fulfillment/workflows/create-return-fulfillment"
import { previewOrderChangeStep, updateReturnsStep } from "../../steps" import { previewOrderChangeStep, updateReturnsStep } from "../../steps"
import { confirmOrderChanges } from "../../steps/confirm-order-changes" import { confirmOrderChanges } from "../../steps/confirm-order-changes"
@@ -58,6 +61,29 @@ export const confirmExchangeRequestValidationStep = createStep(
} }
) )
/**
* This step confirms that a requested exchange has atleast one item to return or send
*/
const confirmIfExchangeItemsArePresent = createStep(
"confirm-if-exchange-items-are-present",
async function ({
exchangeItems,
returnItems,
}: {
exchangeItems: OrderExchangeItemDTO[]
returnItems?: OrderReturnItemDTO[]
}) {
if (exchangeItems.length > 0 && (returnItems || []).length > 0) {
return
}
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Order exchange request should have atleast 1 item inbound and 1 item outbound`
)
}
)
function prepareFulfillmentData({ function prepareFulfillmentData({
order, order,
items, items,
@@ -235,12 +261,18 @@ export const confirmExchangeRequestWorkflow = createWorkflow(
const orderPreview = previewOrderChangeStep(order.id) const orderPreview = previewOrderChangeStep(order.id)
const [createExchangeItems, createdReturnItems] = parallelize( const [createdExchangeItems, createdReturnItems] = parallelize(
createOrderExchangeItemsFromActionsStep(exchangeItems), createOrderExchangeItemsFromActionsStep(exchangeItems),
createReturnItemsFromActionsStep(returnItems), createReturnItemsFromActionsStep(returnItems)
confirmOrderChanges({ changes: [orderChange], orderId: order.id })
) )
confirmIfExchangeItemsArePresent({
exchangeItems: createdExchangeItems,
returnItems: createdReturnItems,
})
confirmOrderChanges({ changes: [orderChange], orderId: order.id })
const returnId = transform( const returnId = transform(
{ createdReturnItems }, { createdReturnItems },
({ createdReturnItems }) => { ({ createdReturnItems }) => {
@@ -261,9 +293,9 @@ export const confirmExchangeRequestWorkflow = createWorkflow(
}) })
const exchangeId = transform( const exchangeId = transform(
{ createExchangeItems }, { createdExchangeItems },
({ createExchangeItems }) => { ({ createdExchangeItems }) => {
return createExchangeItems?.[0]?.exchange_id return createdExchangeItems?.[0]?.exchange_id
} }
) )
@@ -3,10 +3,12 @@ import {
OrderChangeDTO, OrderChangeDTO,
OrderDTO, OrderDTO,
OrderPreviewDTO, OrderPreviewDTO,
OrderReturnItemDTO,
ReturnDTO, ReturnDTO,
} from "@medusajs/types" } from "@medusajs/types"
import { import {
ChangeActionType, ChangeActionType,
MedusaError,
Modules, Modules,
OrderChangeStatus, OrderChangeStatus,
ReturnStatus, ReturnStatus,
@@ -54,6 +56,23 @@ export const confirmReturnRequestValidationStep = createStep(
} }
) )
/**
* This step confirms that a requested return has atleast one item
*/
const confirmIfReturnItemsArePresent = createStep(
"confirm-if-return-items-are-present",
async function ({ returnItems }: { returnItems: OrderReturnItemDTO[] }) {
if (returnItems.length > 0) {
return
}
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Order return request should have atleast 1 item`
)
}
)
function prepareFulfillmentData({ function prepareFulfillmentData({
order, order,
items, items,
@@ -203,6 +222,8 @@ export const confirmReturnRequestWorkflow = createWorkflow(
changes: returnItemActions, changes: returnItemActions,
}) })
confirmIfReturnItemsArePresent({ returnItems: createdReturnItems })
const returnShippingOptionId = transform( const returnShippingOptionId = transform(
{ orderPreview, orderReturn }, { orderPreview, orderReturn },
extractReturnShippingOptionId extractReturnShippingOptionId