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
co-authored by fPolic
parent 17567b9f0a
commit eb590417be
11 changed files with 140 additions and 113 deletions
@@ -1,3 +1,4 @@
import { HttpTypes } from "@medusajs/types"
import { import {
QueryKey, QueryKey,
useMutation, useMutation,
@@ -5,21 +6,25 @@ import {
useQuery, useQuery,
UseQueryOptions, UseQueryOptions,
} from "@tanstack/react-query" } from "@tanstack/react-query"
import { HttpTypes } from "@medusajs/types"
import { sdk } from "../../lib/client" import { sdk } from "../../lib/client"
import { queryClient } from "../../lib/query-client" import { queryClient } from "../../lib/query-client"
import { ordersQueryKeys } from "./orders"
import { queryKeysFactory } from "../../lib/query-key-factory" import { queryKeysFactory } from "../../lib/query-key-factory"
import { ordersQueryKeys } from "./orders"
const CLAIMS_QUERY_KEY = "claims" as const const CLAIMS_QUERY_KEY = "claims" as const
export const claimsQueryKeys = queryKeysFactory(CLAIMS_QUERY_KEY) export const claimsQueryKeys = queryKeysFactory(CLAIMS_QUERY_KEY)
export const useClaim = ( export const useClaim = (
id: string, id: string,
query?: Record<string, any>, query?: HttpTypes.AdminClaimListParams,
options?: Omit< options?: Omit<
UseQueryOptions<any, Error, any, QueryKey>, UseQueryOptions<
HttpTypes.AdminClaimResponse,
Error,
HttpTypes.AdminClaimResponse,
QueryKey
>,
"queryFn" | "queryKey" "queryFn" | "queryKey"
> >
) => { ) => {
@@ -56,10 +61,7 @@ export const useClaims = (
export const useCreateClaim = ( export const useCreateClaim = (
orderId: string, orderId: string,
options?: UseMutationOptions< options?: UseMutationOptions<
{ HttpTypes.AdminClaimResponse,
claim: HttpTypes.AdminClaimResponse
order: HttpTypes.AdminOrderResponse
},
Error, Error,
HttpTypes.AdminCreateClaim HttpTypes.AdminCreateClaim
> >
@@ -278,6 +280,10 @@ export const useRemoveClaimInboundItem = (
queryClient.invalidateQueries({ queryClient.invalidateQueries({
queryKey: ordersQueryKeys.preview(orderId), queryKey: ordersQueryKeys.preview(orderId),
}) })
queryClient.invalidateQueries({
queryKey: ordersQueryKeys.all,
})
options?.onSuccess?.(data, variables, context) options?.onSuccess?.(data, variables, context)
}, },
...options, ...options,
@@ -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 { useTranslation } from "react-i18next"
import { useNavigate, useParams } from "react-router-dom" import { useNavigate, useParams } from "react-router-dom"
import { toast } from "@medusajs/ui"
import { RouteFocusModal } from "../../../components/modals" 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 { useOrder, useOrderPreview } from "../../../hooks/api/orders"
import { useClaims, useCreateClaim } from "../../../hooks/api/claims"
import { DEFAULT_FIELDS } from "../order-detail/constants" import { DEFAULT_FIELDS } from "../order-detail/constants"
import { ClaimCreateForm } from "./components/claim-create-form"
let IS_REQUEST_RUNNING = false let IS_REQUEST_RUNNING = false
@@ -23,28 +21,13 @@ export const ClaimCreate = () => {
}) })
const { order: preview } = useOrderPreview(id!) const { order: preview } = useOrderPreview(id!)
const [activeClaimId, setActiveClaimId] = useState<string>()
const [activeClaimId, setActiveClaimId] = useState()
const { mutateAsync: createClaim } = useCreateClaim(order.id) const { mutateAsync: createClaim } = useCreateClaim(order.id)
// TODO: GET /claims/:id is not implemented const { claim } = useClaim(activeClaimId!, undefined, {
// const { claim } = useClaim(activeClaimId, undefined, {
// enabled: !!activeClaimId,
// })
// TEMP HACK: until the endpoint above is implemented
const { claims } = useClaims(undefined, {
enabled: !!activeClaimId, enabled: !!activeClaimId,
limit: 999,
}) })
const claim = useMemo(() => {
if (claims) {
return claims.find((c) => c.id === activeClaimId)
}
}, [claims, activeClaimId])
useEffect(() => { useEffect(() => {
async function run() { async function run() {
if (IS_REQUEST_RUNNING || !preview) { if (IS_REQUEST_RUNNING || !preview) {
@@ -65,14 +48,15 @@ export const ClaimCreate = () => {
IS_REQUEST_RUNNING = true IS_REQUEST_RUNNING = true
try { try {
const { claim } = await createClaim({ const { claim: createdClaim } = await createClaim({
order_id: preview.id, order_id: preview.id,
type: "replace", type: "replace",
}) })
setActiveClaimId(claim.id)
setActiveClaimId(createdClaim.id)
} catch (e) { } catch (e) {
navigate(`/orders/${preview.id}`, { replace: true })
toast.error(e.message) toast.error(e.message)
navigate(`/orders/${preview.id}`, { replace: true })
} finally { } finally {
IS_REQUEST_RUNNING = false IS_REQUEST_RUNNING = false
} }
@@ -1,19 +1,18 @@
import { OnChangeFn, RowSelectionState } from "@tanstack/react-table"
import { useMemo, useState } from "react"
import { import {
AdminOrderLineItem,
DateComparisonOperator, DateComparisonOperator,
NumericalComparisonOperator, NumericalComparisonOperator,
} from "@medusajs/types" } 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 { useClaimItemTableColumns } from "./use-claim-item-table-columns"
import { useClaimItemTableFilters } from "./use-claim-item-table-filters" import { useClaimItemTableFilters } from "./use-claim-item-table-filters"
import { useClaimItemTableQuery } from "./use-claim-item-table-query" 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 PAGE_SIZE = 50
const PREFIX = "rit" const PREFIX = "rit"
@@ -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 { AdminClaim, AdminOrder, InventoryLevelDTO } 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 { AdminClaim, AdminOrder, InventoryLevelDTO } from "@medusajs/types"
import { PencilSquare } from "@medusajs/icons"
import { import {
RouteFocusModal, RouteFocusModal,
@@ -22,25 +22,26 @@ import {
useStackedModal, useStackedModal,
} from "../../../../../components/modals" } from "../../../../../components/modals"
import { ClaimCreateSchema, ReturnCreateSchemaType } from "./schema"
import { AddClaimItemsTable } from "../add-claim-items-table"
import { Form } from "../../../../../components/common/form" import { Form } from "../../../../../components/common/form"
import { ClaimInboundItem } from "./claim-inbound-item.tsx"
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 { useShippingOptions } from "../../../../../hooks/api/shipping-options"
import { useStockLocations } from "../../../../../hooks/api/stock-locations"
import { getStylizedAmount } from "../../../../../lib/money-amount-helpers" 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 { import {
useAddClaimInboundItems, useAddClaimInboundItems,
useAddClaimInboundShipping, useAddClaimInboundShipping,
useCancelClaimRequest,
useDeleteClaimInboundShipping, useDeleteClaimInboundShipping,
useRemoveClaimInboundItem, useRemoveClaimInboundItem,
useUpdateClaimInboundItem, useUpdateClaimInboundItem,
useUpdateClaimInboundShipping, useUpdateClaimInboundShipping,
} from "../../../../../hooks/api/claims" } from "../../../../../hooks/api/claims"
import { sdk } from "../../../../../lib/client"
import { currencies } from "../../../../../lib/data/currencies"
type ReturnCreateFormProps = { type ReturnCreateFormProps = {
order: AdminOrder order: AdminOrder
@@ -48,7 +49,8 @@ type ReturnCreateFormProps = {
preview: AdminOrder preview: AdminOrder
} }
let selectedItems: string[] = [] let itemsToAdd: string[] = []
let itemsToRemove: string[] = []
let IS_CANCELING = false let IS_CANCELING = false
@@ -87,7 +89,8 @@ export const ClaimCreateForm = ({
*/ */
const { mutateAsync: confirmClaimRequest, isPending: isConfirming } = {} // useConfirmClaimRequest(claim.id, order.id) 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) const { mutateAsync: updateClaimRequest, isPending: isUpdating } = {} // useUpdateClaim(claim.id, order.id)
@@ -244,13 +247,24 @@ export const ClaimCreateForm = ({
} }
}) })
const onItemsSelected = () => { const onItemsSelected = async () => {
addInboundItem({ itemsToAdd.length &&
items: selectedItems.map((id) => ({ (await addInboundItem({
id, items: itemsToAdd.map((id) => ({
quantity: 1, 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) setIsOpen("items", false)
} }
@@ -393,8 +407,18 @@ export const ClaimCreateForm = ({
items={order.items!} items={order.items!}
selectedItems={items.map((i) => i.item_id)} selectedItems={items.map((i) => i.item_id)}
currencyCode={order.currency_code} 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> <StackedFocusModal.Footer>
<div className="flex w-full items-center justify-end gap-x-4"> <div className="flex w-full items-center justify-end gap-x-4">
<div className="flex items-center justify-end gap-x-2"> <div className="flex items-center justify-end gap-x-2">
@@ -413,7 +437,7 @@ export const ClaimCreateForm = ({
variant="primary" variant="primary"
size="small" size="small"
role="button" role="button"
onClick={() => onItemsSelected()} onClick={async () => await onItemsSelected()}
> >
{t("actions.save")} {t("actions.save")}
</Button> </Button>
@@ -423,6 +447,7 @@ export const ClaimCreateForm = ({
</StackedFocusModal.Content> </StackedFocusModal.Content>
</StackedFocusModal> </StackedFocusModal>
</div> </div>
{showPlaceholder && ( {showPlaceholder && (
<div <div
style={{ 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" 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) { {items.map(
removeInboundItem(actionId) (item, index) =>
} previewItemsMap.get(item.item_id) && (
}} <ClaimInboundItem
onUpdate={(payload) => { key={item.id}
const actionId = previewItems item={itemsMap.get(item.item_id)!}
.find((i) => i.id === item.item_id) previewItem={previewItemsMap.get(item.item_id)!}
?.actions?.find((a) => a.action === "RETURN_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 && ( {!showPlaceholder && (
<div className="mt-8 flex flex-col gap-y-4"> <div className="mt-8 flex flex-col gap-y-4">
{/*LOCATION*/} {/*LOCATION*/}
@@ -14,6 +14,7 @@ const DEFAULT_PROPERTIES = [
"shipping_tax_total", "shipping_tax_total",
"tax_total", "tax_total",
"refundable_total", "refundable_total",
"order_change",
] ]
const DEFAULT_RELATIONS = [ const DEFAULT_RELATIONS = [
@@ -86,8 +86,8 @@ export const orderClaimRequestItemReturnWorkflow = createWorkflow(
}) })
const orderReturn: ReturnDTO = transform( const orderReturn: ReturnDTO = transform(
{ createdReturn, existingOrderReturn, orderClaim }, { createdReturn, existingOrderReturn },
({ createdReturn, existingOrderReturn, orderClaim }) => { ({ createdReturn, existingOrderReturn }) => {
return existingOrderReturn ?? (createdReturn?.[0] as ReturnDTO) return existingOrderReturn ?? (createdReturn?.[0] as ReturnDTO)
} }
) )
@@ -102,7 +102,7 @@ export const orderClaimRequestItemReturnWorkflow = createWorkflow(
const orderChange: OrderChangeDTO = useRemoteQueryStep({ const orderChange: OrderChangeDTO = useRemoteQueryStep({
entry_point: "order_change", entry_point: "order_change",
fields: ["id", "status"], fields: ["id", "status", "canceled_at", "confirmed_at", "declined_at"],
variables: { variables: {
filters: { filters: {
order_id: orderClaim.order_id, order_id: orderClaim.order_id,
@@ -113,7 +113,6 @@ export const orderClaimRequestItemReturnWorkflow = createWorkflow(
list: false, list: false,
}).config({ }).config({
name: "order-change-query", name: "order-change-query",
status: [OrderChangeStatus.PENDING, OrderChangeStatus.REQUESTED],
}) })
validationStep({ validationStep({
@@ -6,7 +6,6 @@ import {
} from "@medusajs/types" } from "@medusajs/types"
import { ChangeActionType, OrderChangeStatus } from "@medusajs/utils" import { ChangeActionType, OrderChangeStatus } from "@medusajs/utils"
import { import {
WorkflowData,
WorkflowResponse, WorkflowResponse,
createStep, createStep,
createWorkflow, createWorkflow,
@@ -79,7 +79,17 @@ export const removeItemReturnActionWorkflow = createWorkflow(
const orderChange: OrderChangeDTO = useRemoteQueryStep({ const orderChange: OrderChangeDTO = useRemoteQueryStep({
entry_point: "order_change", 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: { variables: {
filters: { filters: {
order_id: orderReturn.order_id, order_id: orderReturn.order_id,
+3 -3
View File
@@ -1,8 +1,8 @@
import { OperatorMap } from "../../dal" import { OperatorMap } from "../../dal"
import { FindParams } from "../common"
import { ClaimReason, ReturnDTO } from "../../order" import { ClaimReason, ReturnDTO } from "../../order"
import { BaseOrder } from "../order/common"
import { BigNumberRawValue } from "../../totals" import { BigNumberRawValue } from "../../totals"
import { FindParams } from "../common"
import { BaseOrder } from "../order/common"
export interface BaseClaimItem { export interface BaseClaimItem {
id: string id: string
@@ -20,7 +20,7 @@ export interface BaseClaimItem {
export interface BaseClaim export interface BaseClaim
extends Omit<BaseOrder, "status" | "version" | "items"> { extends Omit<BaseOrder, "status" | "version" | "items"> {
order_id: string order_id: string
claim_items: BaseClaimItem claim_items: BaseClaimItem[]
additional_items: any[] additional_items: any[]
return?: ReturnDTO return?: ReturnDTO
return_id?: string return_id?: string
@@ -10,6 +10,7 @@ import {
AuthenticatedMedusaRequest, AuthenticatedMedusaRequest,
MedusaResponse, MedusaResponse,
} from "../../../../../../../types/routing" } from "../../../../../../../types/routing"
import { refetchEntity } from "../../../../../../utils/refetch-entity"
import { defaultAdminDetailsReturnFields } from "../../../../../returns/query-config" import { defaultAdminDetailsReturnFields } from "../../../../../returns/query-config"
import { AdminPostReturnsRequestItemsActionReqSchemaType } from "../../../../../returns/validators" import { AdminPostReturnsRequestItemsActionReqSchemaType } from "../../../../../returns/validators"
@@ -64,30 +65,28 @@ export const DELETE = async (
req: AuthenticatedMedusaRequest, req: AuthenticatedMedusaRequest,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const { id, action_id } = req.params const { id, action_id } = req.params
const claim = await refetchEntity("order_claim", id, req.scope, ["return_id"])
const { result: orderPreview } = await removeItemReturnActionWorkflow( const { result: orderPreview } = await removeItemReturnActionWorkflow(
req.scope req.scope
).run({ ).run({
input: { input: {
return_id: id, return_id: claim.return_id,
action_id, action_id,
}, },
}) })
const queryObject = remoteQueryObjectFromString({ const orderReturn = await refetchEntity(
entryPoint: "return", "return",
variables: { {
...req.filterableFields,
id, id,
filters: {
...req.filterableFields,
},
}, },
fields: req.remoteQueryConfig.fields, req.scope,
}) defaultAdminDetailsReturnFields
const [orderReturn] = await remoteQuery(queryObject) )
res.json({ res.json({
order_preview: orderPreview, order_preview: orderPreview,
@@ -1,5 +1,5 @@
import { import {
removeReturnShippingMethodWorkflow, removeClaimShippingMethodWorkflow,
updateReturnShippingMethodWorkflow, updateReturnShippingMethodWorkflow,
} from "@medusajs/core-flows" } from "@medusajs/core-flows"
import { import {
@@ -76,7 +76,7 @@ export const DELETE = async (
variables: { variables: {
id, id,
}, },
fields: ["return_id"], fields: ["id", "return_id"],
}), }),
undefined, undefined,
{ {
@@ -84,11 +84,11 @@ export const DELETE = async (
} }
) )
const { result: orderPreview } = await removeReturnShippingMethodWorkflow( const { result: orderPreview } = await removeClaimShippingMethodWorkflow(
req.scope req.scope
).run({ ).run({
input: { input: {
return_id: claim.return_id, claim_id: claim.id,
action_id, action_id,
}, },
}) })