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:
@@ -427,6 +427,30 @@ medusaIntegrationTestRunner({
|
||||
},
|
||||
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(
|
||||
`/admin/exchanges/${exchangeId2}/request`,
|
||||
{},
|
||||
|
||||
@@ -36,7 +36,7 @@ import { AddClaimItemsTable } from "../add-claim-items-table"
|
||||
import { ClaimInboundItem } from "./claim-inbound-item.tsx"
|
||||
import { ClaimCreateSchema, CreateClaimSchemaType } from "./schema"
|
||||
|
||||
import { AdminReturn } from "@medusajs/types"
|
||||
import { AdminReturn, HttpTypes } from "@medusajs/types"
|
||||
import {
|
||||
useAddClaimInboundItems,
|
||||
useAddClaimInboundShipping,
|
||||
@@ -275,7 +275,10 @@ export const ClaimCreateForm = ({
|
||||
})
|
||||
}
|
||||
} 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) => {
|
||||
const actionId = previewItems
|
||||
onUpdate={(payload: HttpTypes.AdminUpdateReturnItems) => {
|
||||
const action = previewItems
|
||||
.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(
|
||||
{ ...payload, actionId },
|
||||
{ ...payload, actionId: action.id },
|
||||
{
|
||||
onError: (error) => {
|
||||
if (
|
||||
action.details?.quantity &&
|
||||
payload.quantity
|
||||
) {
|
||||
form.setValue(
|
||||
`inbound_items.${index}.quantity`,
|
||||
action.details?.quantity as number
|
||||
)
|
||||
}
|
||||
|
||||
toast.error(error.message)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { useEffect, useMemo, useState } from "react"
|
||||
import { useFieldArray, UseFormReturn } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
import { Combobox } from "../../../../../components/inputs/combobox"
|
||||
import {
|
||||
@@ -134,11 +135,14 @@ export const ClaimOutboundSection = ({
|
||||
})
|
||||
}
|
||||
} else {
|
||||
append({
|
||||
item_id: i.id,
|
||||
quantity: i.detail.quantity,
|
||||
variant_id: i.variant_id,
|
||||
})
|
||||
append(
|
||||
{
|
||||
item_id: i.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
|
||||
.find((i) => i.id === item.item_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 { useTranslation } from "react-i18next"
|
||||
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
import { Combobox } from "../../../../../components/inputs/combobox"
|
||||
import {
|
||||
@@ -172,7 +173,10 @@ export const ExchangeInboundSection = ({
|
||||
})
|
||||
}
|
||||
} 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) => {
|
||||
const actionId = previewInboundItems
|
||||
onUpdate={(payload: HttpTypes.AdminUpdateReturnItems) => {
|
||||
const action = previewInboundItems
|
||||
.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(
|
||||
{ ...payload, actionId },
|
||||
{ ...payload, actionId: action.id },
|
||||
{
|
||||
onError: (error) => {
|
||||
if (action.details?.quantity && payload.quantity) {
|
||||
form.setValue(
|
||||
`inbound_items.${index}.quantity`,
|
||||
action.details?.quantity as number
|
||||
)
|
||||
}
|
||||
|
||||
toast.error(error.message)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -134,11 +134,14 @@ export const ExchangeOutboundSection = ({
|
||||
})
|
||||
}
|
||||
} else {
|
||||
append({
|
||||
item_id: i.id,
|
||||
quantity: i.detail.quantity,
|
||||
variant_id: i.variant_id,
|
||||
})
|
||||
append(
|
||||
{
|
||||
item_id: i.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 { PencilSquare } from "@medusajs/icons"
|
||||
import { AdminOrder, InventoryLevelDTO, ReturnDTO } from "@medusajs/types"
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
@@ -10,10 +11,9 @@ import {
|
||||
Text,
|
||||
toast,
|
||||
} from "@medusajs/ui"
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import { useFieldArray, useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { AdminOrder, InventoryLevelDTO, ReturnDTO } from "@medusajs/types"
|
||||
import { PencilSquare } from "@medusajs/icons"
|
||||
|
||||
import {
|
||||
RouteFocusModal,
|
||||
@@ -22,14 +22,8 @@ import {
|
||||
useStackedModal,
|
||||
} from "../../../../../components/modals"
|
||||
|
||||
import { ReturnCreateSchema, ReturnCreateSchemaType } from "./schema"
|
||||
import { AddReturnItemsTable } from "../add-return-items-table"
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
import { ReturnItem } from "./return-item"
|
||||
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 {
|
||||
useAddReturnItem,
|
||||
useAddReturnShipping,
|
||||
@@ -41,8 +35,14 @@ import {
|
||||
useUpdateReturnItem,
|
||||
useUpdateReturnShipping,
|
||||
} 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 { 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 = {
|
||||
order: AdminOrder
|
||||
@@ -455,12 +455,26 @@ export const ReturnCreateForm = ({
|
||||
}
|
||||
}}
|
||||
onUpdate={(payload) => {
|
||||
const actionId = previewItems
|
||||
const action = previewItems
|
||||
.find((i) => i.id === item.item_id)
|
||||
?.actions?.find((a) => a.action === "RETURN_ITEM")?.id
|
||||
?.actions?.find((a) => a.action === "RETURN_ITEM")
|
||||
|
||||
if (actionId) {
|
||||
updateReturnItem({ ...payload, actionId })
|
||||
if (action) {
|
||||
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}
|
||||
|
||||
@@ -3,26 +3,29 @@ import {
|
||||
OrderChangeActionDTO,
|
||||
OrderChangeDTO,
|
||||
OrderClaimDTO,
|
||||
OrderClaimItemDTO,
|
||||
OrderDTO,
|
||||
OrderPreviewDTO,
|
||||
OrderReturnItemDTO,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
ChangeActionType,
|
||||
MedusaError,
|
||||
Modules,
|
||||
OrderChangeStatus,
|
||||
ReturnStatus,
|
||||
} from "@medusajs/utils"
|
||||
import {
|
||||
WorkflowResponse,
|
||||
createStep,
|
||||
createWorkflow,
|
||||
parallelize,
|
||||
transform,
|
||||
when,
|
||||
WorkflowResponse,
|
||||
} from "@medusajs/workflows-sdk"
|
||||
import { createRemoteLinkStep, useRemoteQueryStep } from "../../../common"
|
||||
import { reserveInventoryStep } from "../../../cart/steps/reserve-inventory"
|
||||
import { prepareConfirmInventoryInput } from "../../../cart/utils/prepare-confirm-inventory-input"
|
||||
import { createRemoteLinkStep, useRemoteQueryStep } from "../../../common"
|
||||
import { createReturnFulfillmentWorkflow } from "../../../fulfillment/workflows/create-return-fulfillment"
|
||||
import { previewOrderChangeStep, updateReturnsStep } from "../../steps"
|
||||
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({
|
||||
order,
|
||||
items,
|
||||
@@ -188,7 +214,13 @@ export const confirmClaimRequestWorkflow = createWorkflow(
|
||||
): WorkflowResponse<OrderPreviewDTO> {
|
||||
const orderClaim: OrderClaimDTO = useRemoteQueryStep({
|
||||
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 },
|
||||
list: false,
|
||||
throw_if_key_not_found: true,
|
||||
@@ -241,12 +273,16 @@ export const confirmClaimRequestWorkflow = createWorkflow(
|
||||
|
||||
const orderPreview = previewOrderChangeStep(order.id)
|
||||
|
||||
const [createClaimItems, createdReturnItems] = parallelize(
|
||||
const [createdClaimItems, createdReturnItems] = parallelize(
|
||||
createOrderClaimItemsFromActionsStep(claimItems),
|
||||
createReturnItemsFromActionsStep(returnItems),
|
||||
confirmOrderChanges({ changes: [orderChange], orderId: order.id })
|
||||
createReturnItemsFromActionsStep(returnItems)
|
||||
)
|
||||
|
||||
confirmIfClaimItemsArePresent({
|
||||
claimItems: createdClaimItems,
|
||||
returnItems: createdReturnItems,
|
||||
})
|
||||
|
||||
const returnId = transform(
|
||||
{ createdReturnItems },
|
||||
({ createdReturnItems }) => {
|
||||
@@ -254,6 +290,8 @@ export const confirmClaimRequestWorkflow = createWorkflow(
|
||||
}
|
||||
)
|
||||
|
||||
confirmOrderChanges({ changes: [orderChange], orderId: order.id })
|
||||
|
||||
when({ returnId }, ({ returnId }) => {
|
||||
return !!returnId
|
||||
}).then(() => {
|
||||
@@ -266,9 +304,12 @@ export const confirmClaimRequestWorkflow = createWorkflow(
|
||||
])
|
||||
})
|
||||
|
||||
const claimId = transform({ createClaimItems }, ({ createClaimItems }) => {
|
||||
return createClaimItems?.[0]?.claim_id
|
||||
})
|
||||
const claimId = transform(
|
||||
{ createdClaimItems },
|
||||
({ createdClaimItems }) => {
|
||||
return createdClaimItems?.[0]?.claim_id
|
||||
}
|
||||
)
|
||||
|
||||
const { returnShippingMethod, claimShippingMethod } = transform(
|
||||
{ orderPreview, orderClaim, returnId },
|
||||
|
||||
@@ -4,10 +4,13 @@ import {
|
||||
OrderChangeDTO,
|
||||
OrderDTO,
|
||||
OrderExchangeDTO,
|
||||
OrderExchangeItemDTO,
|
||||
OrderPreviewDTO,
|
||||
OrderReturnItemDTO,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
ChangeActionType,
|
||||
MedusaError,
|
||||
Modules,
|
||||
OrderChangeStatus,
|
||||
ReturnStatus,
|
||||
@@ -20,9 +23,9 @@ import {
|
||||
transform,
|
||||
when,
|
||||
} from "@medusajs/workflows-sdk"
|
||||
import { createRemoteLinkStep, useRemoteQueryStep } from "../../../common"
|
||||
import { reserveInventoryStep } from "../../../cart/steps/reserve-inventory"
|
||||
import { prepareConfirmInventoryInput } from "../../../cart/utils/prepare-confirm-inventory-input"
|
||||
import { createRemoteLinkStep, useRemoteQueryStep } from "../../../common"
|
||||
import { createReturnFulfillmentWorkflow } from "../../../fulfillment/workflows/create-return-fulfillment"
|
||||
import { previewOrderChangeStep, updateReturnsStep } from "../../steps"
|
||||
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({
|
||||
order,
|
||||
items,
|
||||
@@ -235,12 +261,18 @@ export const confirmExchangeRequestWorkflow = createWorkflow(
|
||||
|
||||
const orderPreview = previewOrderChangeStep(order.id)
|
||||
|
||||
const [createExchangeItems, createdReturnItems] = parallelize(
|
||||
const [createdExchangeItems, createdReturnItems] = parallelize(
|
||||
createOrderExchangeItemsFromActionsStep(exchangeItems),
|
||||
createReturnItemsFromActionsStep(returnItems),
|
||||
confirmOrderChanges({ changes: [orderChange], orderId: order.id })
|
||||
createReturnItemsFromActionsStep(returnItems)
|
||||
)
|
||||
|
||||
confirmIfExchangeItemsArePresent({
|
||||
exchangeItems: createdExchangeItems,
|
||||
returnItems: createdReturnItems,
|
||||
})
|
||||
|
||||
confirmOrderChanges({ changes: [orderChange], orderId: order.id })
|
||||
|
||||
const returnId = transform(
|
||||
{ createdReturnItems },
|
||||
({ createdReturnItems }) => {
|
||||
@@ -261,9 +293,9 @@ export const confirmExchangeRequestWorkflow = createWorkflow(
|
||||
})
|
||||
|
||||
const exchangeId = transform(
|
||||
{ createExchangeItems },
|
||||
({ createExchangeItems }) => {
|
||||
return createExchangeItems?.[0]?.exchange_id
|
||||
{ createdExchangeItems },
|
||||
({ createdExchangeItems }) => {
|
||||
return createdExchangeItems?.[0]?.exchange_id
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -3,10 +3,12 @@ import {
|
||||
OrderChangeDTO,
|
||||
OrderDTO,
|
||||
OrderPreviewDTO,
|
||||
OrderReturnItemDTO,
|
||||
ReturnDTO,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
ChangeActionType,
|
||||
MedusaError,
|
||||
Modules,
|
||||
OrderChangeStatus,
|
||||
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({
|
||||
order,
|
||||
items,
|
||||
@@ -203,6 +222,8 @@ export const confirmReturnRequestWorkflow = createWorkflow(
|
||||
changes: returnItemActions,
|
||||
})
|
||||
|
||||
confirmIfReturnItemsArePresent({ returnItems: createdReturnItems })
|
||||
|
||||
const returnShippingOptionId = transform(
|
||||
{ orderPreview, orderReturn },
|
||||
extractReturnShippingOptionId
|
||||
|
||||
Reference in New Issue
Block a user