feat(core-flows,dashboard,medusa): ability to add and remove items to claim inbound (#8480)

* wip: setup UI

* wip: rendering modal, adding claim items, create checks

* fix: make form work after merge

* fix: continuation of claim edit

* chore: ability to add and remove items to claim inbound

* chore: minor fixes

---------

Co-authored-by: fPolic <mainacc.polic@gmail.com>
This commit is contained in:
Riqwan Thamir
2024-08-07 15:27:04 +02:00
committed by GitHub
parent 17567b9f0a
commit eb590417be
11 changed files with 140 additions and 113 deletions

View File

@@ -1,3 +1,4 @@
import { HttpTypes } from "@medusajs/types"
import {
QueryKey,
useMutation,
@@ -5,21 +6,25 @@ import {
useQuery,
UseQueryOptions,
} from "@tanstack/react-query"
import { HttpTypes } from "@medusajs/types"
import { sdk } from "../../lib/client"
import { queryClient } from "../../lib/query-client"
import { ordersQueryKeys } from "./orders"
import { queryKeysFactory } from "../../lib/query-key-factory"
import { ordersQueryKeys } from "./orders"
const CLAIMS_QUERY_KEY = "claims" as const
export const claimsQueryKeys = queryKeysFactory(CLAIMS_QUERY_KEY)
export const useClaim = (
id: string,
query?: Record<string, any>,
query?: HttpTypes.AdminClaimListParams,
options?: Omit<
UseQueryOptions<any, Error, any, QueryKey>,
UseQueryOptions<
HttpTypes.AdminClaimResponse,
Error,
HttpTypes.AdminClaimResponse,
QueryKey
>,
"queryFn" | "queryKey"
>
) => {
@@ -56,10 +61,7 @@ export const useClaims = (
export const useCreateClaim = (
orderId: string,
options?: UseMutationOptions<
{
claim: HttpTypes.AdminClaimResponse
order: HttpTypes.AdminOrderResponse
},
HttpTypes.AdminClaimResponse,
Error,
HttpTypes.AdminCreateClaim
>
@@ -278,6 +280,10 @@ export const useRemoveClaimInboundItem = (
queryClient.invalidateQueries({
queryKey: ordersQueryKeys.preview(orderId),
})
queryClient.invalidateQueries({
queryKey: ordersQueryKeys.all,
})
options?.onSuccess?.(data, variables, context)
},
...options,

View File

@@ -1,15 +1,13 @@
import { useEffect, useMemo, useState } from "react"
import { toast } from "@medusajs/ui"
import { useEffect, useState } from "react"
import { useTranslation } from "react-i18next"
import { useNavigate, useParams } from "react-router-dom"
import { toast } from "@medusajs/ui"
import { RouteFocusModal } from "../../../components/modals"
import { ClaimCreateForm } from "./components/claim-create-form"
import { useClaim, useCreateClaim } from "../../../hooks/api/claims"
import { useOrder, useOrderPreview } from "../../../hooks/api/orders"
import { useClaims, useCreateClaim } from "../../../hooks/api/claims"
import { DEFAULT_FIELDS } from "../order-detail/constants"
import { ClaimCreateForm } from "./components/claim-create-form"
let IS_REQUEST_RUNNING = false
@@ -23,28 +21,13 @@ export const ClaimCreate = () => {
})
const { order: preview } = useOrderPreview(id!)
const [activeClaimId, setActiveClaimId] = useState()
const [activeClaimId, setActiveClaimId] = useState<string>()
const { mutateAsync: createClaim } = useCreateClaim(order.id)
// TODO: GET /claims/:id is not implemented
// const { claim } = useClaim(activeClaimId, undefined, {
// enabled: !!activeClaimId,
// })
// TEMP HACK: until the endpoint above is implemented
const { claims } = useClaims(undefined, {
const { claim } = useClaim(activeClaimId!, undefined, {
enabled: !!activeClaimId,
limit: 999,
})
const claim = useMemo(() => {
if (claims) {
return claims.find((c) => c.id === activeClaimId)
}
}, [claims, activeClaimId])
useEffect(() => {
async function run() {
if (IS_REQUEST_RUNNING || !preview) {
@@ -65,14 +48,15 @@ export const ClaimCreate = () => {
IS_REQUEST_RUNNING = true
try {
const { claim } = await createClaim({
const { claim: createdClaim } = await createClaim({
order_id: preview.id,
type: "replace",
})
setActiveClaimId(claim.id)
setActiveClaimId(createdClaim.id)
} catch (e) {
navigate(`/orders/${preview.id}`, { replace: true })
toast.error(e.message)
navigate(`/orders/${preview.id}`, { replace: true })
} finally {
IS_REQUEST_RUNNING = false
}

View File

@@ -1,19 +1,18 @@
import { OnChangeFn, RowSelectionState } from "@tanstack/react-table"
import { useMemo, useState } from "react"
import {
AdminOrderLineItem,
DateComparisonOperator,
NumericalComparisonOperator,
} from "@medusajs/types"
import { AdminOrderLineItem } from "@medusajs/types"
import { OnChangeFn, RowSelectionState } from "@tanstack/react-table"
import { useMemo, useState } from "react"
import { DataTable } from "../../../../../components/table/data-table"
import { useDataTable } from "../../../../../hooks/use-data-table"
import { getStylizedAmount } from "../../../../../lib/money-amount-helpers"
import { getReturnableQuantity } from "../../../../../lib/rma"
import { useClaimItemTableColumns } from "./use-claim-item-table-columns"
import { useClaimItemTableFilters } from "./use-claim-item-table-filters"
import { useClaimItemTableQuery } from "./use-claim-item-table-query"
import { useDataTable } from "../../../../../hooks/use-data-table"
import { DataTable } from "../../../../../components/table/data-table"
import { getStylizedAmount } from "../../../../../lib/money-amount-helpers"
import { getReturnableQuantity } from "../../../../../lib/rma"
const PAGE_SIZE = 50
const PREFIX = "rit"

View File

@@ -1,5 +1,6 @@
import React, { useEffect, useMemo, useState } from "react"
import { zodResolver } from "@hookform/resolvers/zod"
import { PencilSquare } from "@medusajs/icons"
import { AdminClaim, AdminOrder, InventoryLevelDTO } 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 { AdminClaim, AdminOrder, InventoryLevelDTO } from "@medusajs/types"
import { PencilSquare } from "@medusajs/icons"
import {
RouteFocusModal,
@@ -22,25 +22,26 @@ import {
useStackedModal,
} from "../../../../../components/modals"
import { ClaimCreateSchema, ReturnCreateSchemaType } from "./schema"
import { AddClaimItemsTable } from "../add-claim-items-table"
import { Form } from "../../../../../components/common/form"
import { ClaimInboundItem } from "./claim-inbound-item.tsx"
import { Combobox } from "../../../../../components/inputs/combobox"
import { useStockLocations } from "../../../../../hooks/api/stock-locations"
import { useShippingOptions } from "../../../../../hooks/api/shipping-options"
import { useStockLocations } from "../../../../../hooks/api/stock-locations"
import { getStylizedAmount } from "../../../../../lib/money-amount-helpers"
import { AddClaimItemsTable } from "../add-claim-items-table"
import { ClaimInboundItem } from "./claim-inbound-item.tsx"
import { ClaimCreateSchema, ReturnCreateSchemaType } from "./schema"
import { currencies } from "../../../../../lib/data/currencies"
import { sdk } from "../../../../../lib/client"
import {
useAddClaimInboundItems,
useAddClaimInboundShipping,
useCancelClaimRequest,
useDeleteClaimInboundShipping,
useRemoveClaimInboundItem,
useUpdateClaimInboundItem,
useUpdateClaimInboundShipping,
} from "../../../../../hooks/api/claims"
import { sdk } from "../../../../../lib/client"
import { currencies } from "../../../../../lib/data/currencies"
type ReturnCreateFormProps = {
order: AdminOrder
@@ -48,7 +49,8 @@ type ReturnCreateFormProps = {
preview: AdminOrder
}
let selectedItems: string[] = []
let itemsToAdd: string[] = []
let itemsToRemove: string[] = []
let IS_CANCELING = false
@@ -87,7 +89,8 @@ export const ClaimCreateForm = ({
*/
const { mutateAsync: confirmClaimRequest, isPending: isConfirming } = {} // useConfirmClaimRequest(claim.id, order.id)
const { mutateAsync: cancelClaimRequest, isPending: isCanceling } = {} // useCancelClaimRequest(claim.id, order.id)
const { mutateAsync: cancelClaimRequest, isPending: isCanceling } =
useCancelClaimRequest(claim.id, order.id)
const { mutateAsync: updateClaimRequest, isPending: isUpdating } = {} // useUpdateClaim(claim.id, order.id)
@@ -244,13 +247,24 @@ export const ClaimCreateForm = ({
}
})
const onItemsSelected = () => {
addInboundItem({
items: selectedItems.map((id) => ({
id,
quantity: 1,
})),
})
const onItemsSelected = async () => {
itemsToAdd.length &&
(await addInboundItem({
items: itemsToAdd.map((id) => ({
id,
quantity: 1,
})),
}))
for (const itemToRemove of itemsToRemove) {
const actionId = previewItems
.find((i) => i.id === itemToRemove)
?.actions?.find((a) => a.action === "RETURN_ITEM")?.id
if (actionId) {
await removeInboundItem(actionId)
}
}
setIsOpen("items", false)
}
@@ -393,8 +407,18 @@ export const ClaimCreateForm = ({
items={order.items!}
selectedItems={items.map((i) => i.item_id)}
currencyCode={order.currency_code}
onSelectionChange={(s) => (selectedItems = s)}
onSelectionChange={(finalSelection) => {
const alreadySelected = items.map((i) => i.item_id)
itemsToAdd = finalSelection.filter(
(selection) => !alreadySelected.includes(selection)
)
itemsToRemove = alreadySelected.filter(
(selection) => !finalSelection.includes(selection)
)
}}
/>
<StackedFocusModal.Footer>
<div className="flex w-full items-center justify-end gap-x-4">
<div className="flex items-center justify-end gap-x-2">
@@ -413,7 +437,7 @@ export const ClaimCreateForm = ({
variant="primary"
size="small"
role="button"
onClick={() => onItemsSelected()}
onClick={async () => await onItemsSelected()}
>
{t("actions.save")}
</Button>
@@ -423,6 +447,7 @@ export const ClaimCreateForm = ({
</StackedFocusModal.Content>
</StackedFocusModal>
</div>
{showPlaceholder && (
<div
style={{
@@ -432,34 +457,39 @@ export const ClaimCreateForm = ({
className="bg-ui-bg-field mt-4 block h-[56px] w-full rounded-lg border border-dashed"
/>
)}
{items.map((item, index) => (
<ClaimInboundItem
key={item.id}
item={itemsMap.get(item.item_id)!}
previewItem={previewItemsMap.get(item.item_id)!}
currencyCode={order.currency_code}
form={form}
onRemove={() => {
const actionId = previewItems
.find((i) => i.id === item.item_id)
?.actions?.find((a) => a.action === "RETURN_ITEM")?.id
if (actionId) {
removeInboundItem(actionId)
}
}}
onUpdate={(payload) => {
const actionId = previewItems
.find((i) => i.id === item.item_id)
?.actions?.find((a) => a.action === "RETURN_ITEM")?.id
{items.map(
(item, index) =>
previewItemsMap.get(item.item_id) && (
<ClaimInboundItem
key={item.id}
item={itemsMap.get(item.item_id)!}
previewItem={previewItemsMap.get(item.item_id)!}
currencyCode={order.currency_code}
form={form}
onRemove={() => {
const actionId = previewItems
.find((i) => i.id === item.item_id)
?.actions?.find((a) => a.action === "RETURN_ITEM")?.id
if (actionId) {
removeInboundItem(actionId)
}
}}
onUpdate={(payload) => {
const actionId = previewItems
.find((i) => i.id === item.item_id)
?.actions?.find((a) => a.action === "RETURN_ITEM")?.id
if (actionId) {
updateInboundItem({ ...payload, actionId })
}
}}
index={index}
/>
)
)}
if (actionId) {
updateInboundItem({ ...payload, actionId })
}
}}
index={index}
/>
))}
{!showPlaceholder && (
<div className="mt-8 flex flex-col gap-y-4">
{/*LOCATION*/}

View File

@@ -14,6 +14,7 @@ const DEFAULT_PROPERTIES = [
"shipping_tax_total",
"tax_total",
"refundable_total",
"order_change",
]
const DEFAULT_RELATIONS = [

View File

@@ -86,8 +86,8 @@ export const orderClaimRequestItemReturnWorkflow = createWorkflow(
})
const orderReturn: ReturnDTO = transform(
{ createdReturn, existingOrderReturn, orderClaim },
({ createdReturn, existingOrderReturn, orderClaim }) => {
{ createdReturn, existingOrderReturn },
({ createdReturn, existingOrderReturn }) => {
return existingOrderReturn ?? (createdReturn?.[0] as ReturnDTO)
}
)
@@ -102,7 +102,7 @@ export const orderClaimRequestItemReturnWorkflow = createWorkflow(
const orderChange: OrderChangeDTO = useRemoteQueryStep({
entry_point: "order_change",
fields: ["id", "status"],
fields: ["id", "status", "canceled_at", "confirmed_at", "declined_at"],
variables: {
filters: {
order_id: orderClaim.order_id,
@@ -113,7 +113,6 @@ export const orderClaimRequestItemReturnWorkflow = createWorkflow(
list: false,
}).config({
name: "order-change-query",
status: [OrderChangeStatus.PENDING, OrderChangeStatus.REQUESTED],
})
validationStep({

View File

@@ -6,7 +6,6 @@ import {
} from "@medusajs/types"
import { ChangeActionType, OrderChangeStatus } from "@medusajs/utils"
import {
WorkflowData,
WorkflowResponse,
createStep,
createWorkflow,

View File

@@ -79,7 +79,17 @@ export const removeItemReturnActionWorkflow = createWorkflow(
const orderChange: OrderChangeDTO = useRemoteQueryStep({
entry_point: "order_change",
fields: ["id", "status", "version", "actions.*"],
fields: [
"id",
"status",
"version",
"return_id",
"order_id",
"actions.*",
"canceled_at",
"confirmed_at",
"declined_at",
],
variables: {
filters: {
order_id: orderReturn.order_id,

View File

@@ -1,8 +1,8 @@
import { OperatorMap } from "../../dal"
import { FindParams } from "../common"
import { ClaimReason, ReturnDTO } from "../../order"
import { BaseOrder } from "../order/common"
import { BigNumberRawValue } from "../../totals"
import { FindParams } from "../common"
import { BaseOrder } from "../order/common"
export interface BaseClaimItem {
id: string
@@ -20,7 +20,7 @@ export interface BaseClaimItem {
export interface BaseClaim
extends Omit<BaseOrder, "status" | "version" | "items"> {
order_id: string
claim_items: BaseClaimItem
claim_items: BaseClaimItem[]
additional_items: any[]
return?: ReturnDTO
return_id?: string

View File

@@ -10,6 +10,7 @@ import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../../../../../types/routing"
import { refetchEntity } from "../../../../../../utils/refetch-entity"
import { defaultAdminDetailsReturnFields } from "../../../../../returns/query-config"
import { AdminPostReturnsRequestItemsActionReqSchemaType } from "../../../../../returns/validators"
@@ -64,30 +65,28 @@ export const DELETE = async (
req: AuthenticatedMedusaRequest,
res: MedusaResponse
) => {
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const { id, action_id } = req.params
const claim = await refetchEntity("order_claim", id, req.scope, ["return_id"])
const { result: orderPreview } = await removeItemReturnActionWorkflow(
req.scope
).run({
input: {
return_id: id,
return_id: claim.return_id,
action_id,
},
})
const queryObject = remoteQueryObjectFromString({
entryPoint: "return",
variables: {
const orderReturn = await refetchEntity(
"return",
{
...req.filterableFields,
id,
filters: {
...req.filterableFields,
},
},
fields: req.remoteQueryConfig.fields,
})
const [orderReturn] = await remoteQuery(queryObject)
req.scope,
defaultAdminDetailsReturnFields
)
res.json({
order_preview: orderPreview,

View File

@@ -1,5 +1,5 @@
import {
removeReturnShippingMethodWorkflow,
removeClaimShippingMethodWorkflow,
updateReturnShippingMethodWorkflow,
} from "@medusajs/core-flows"
import {
@@ -76,7 +76,7 @@ export const DELETE = async (
variables: {
id,
},
fields: ["return_id"],
fields: ["id", "return_id"],
}),
undefined,
{
@@ -84,11 +84,11 @@ export const DELETE = async (
}
)
const { result: orderPreview } = await removeReturnShippingMethodWorkflow(
const { result: orderPreview } = await removeClaimShippingMethodWorkflow(
req.scope
).run({
input: {
return_id: claim.return_id,
claim_id: claim.id,
action_id,
},
})