fix(dashboard, core-flows): improvements to order page on canceled orders (#10888)
what: - Remove pending payment for canceled orders - Hide unfulfilled items for canceled orders - Disable non refundable payments from being refunded - Populate refund created_by - Disable order edit when canceled - Fix bug https://github.com/medusajs/medusa/issues/10852 RESOLVES CMRC-842
This commit is contained in:
1 parent
7232a8a930
commit
1758bfb8d0
9 files changed
+67
-51
No files matched your search
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/dashboard": patch
|
||||||
|
"@medusajs/core-flows": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix(dashboard, core-flows): improvements to order page on canceled orders
|
||||||
@@ -443,11 +443,12 @@ medusaIntegrationTestRunner({
|
|||||||
amount: 106,
|
amount: 106,
|
||||||
payments: [
|
payments: [
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
// canceled_at: expect.any(String),
|
canceled_at: null,
|
||||||
refunds: [
|
refunds: [
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
amount: 106,
|
amount: 106,
|
||||||
|
created_by: expect.any(String),
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
captures: [
|
captures: [
|
||||||
@@ -488,11 +489,9 @@ medusaIntegrationTestRunner({
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
const response = await api.post(
|
const response = await api
|
||||||
`/admin/orders/${order.id}/cancel`,
|
.post(`/admin/orders/${order.id}/cancel`, {}, adminHeaders)
|
||||||
{},
|
.catch((e) => e)
|
||||||
adminHeaders
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(response.status).toBe(200)
|
expect(response.status).toBe(200)
|
||||||
expect(response.data.order).toEqual(
|
expect(response.data.order).toEqual(
|
||||||
@@ -514,11 +513,11 @@ medusaIntegrationTestRunner({
|
|||||||
amount: 106,
|
amount: 106,
|
||||||
payments: [
|
payments: [
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
// canceled_at: expect.any(String),
|
|
||||||
refunds: [
|
refunds: [
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
amount: 50,
|
amount: 50,
|
||||||
|
created_by: expect.any(String),
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
captures: [
|
captures: [
|
||||||
|
|||||||
+23
-14
@@ -44,8 +44,7 @@ export const CreateRefundForm = ({
|
|||||||
const paymentId = searchParams.get("paymentId")
|
const paymentId = searchParams.get("paymentId")
|
||||||
const payments = getPaymentsFromOrder(order)
|
const payments = getPaymentsFromOrder(order)
|
||||||
const payment = payments.find((p) => p.id === paymentId)!
|
const payment = payments.find((p) => p.id === paymentId)!
|
||||||
const paymentAmount = (payment?.amount || 0) as number
|
const paymentAmount = payment.amount || 0
|
||||||
|
|
||||||
const form = useForm<zod.infer<typeof CreateRefundSchema>>({
|
const form = useForm<zod.infer<typeof CreateRefundSchema>>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
amount: paymentAmount,
|
amount: paymentAmount,
|
||||||
@@ -121,8 +120,20 @@ export const CreateRefundForm = ({
|
|||||||
</Select.Trigger>
|
</Select.Trigger>
|
||||||
|
|
||||||
<Select.Content>
|
<Select.Content>
|
||||||
{payments.map((payment) => (
|
{payments.map((payment) => {
|
||||||
<Select.Item value={payment!.id} key={payment.id}>
|
const totalRefunded = payment.refunds.reduce(
|
||||||
|
(acc, next) => next.amount + acc,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select.Item
|
||||||
|
value={payment!.id}
|
||||||
|
key={payment.id}
|
||||||
|
disabled={
|
||||||
|
!!payment.canceled_at || totalRefunded >= payment.amount
|
||||||
|
}
|
||||||
|
>
|
||||||
<span>
|
<span>
|
||||||
{getLocaleAmount(
|
{getLocaleAmount(
|
||||||
payment.amount as number,
|
payment.amount as number,
|
||||||
@@ -133,7 +144,8 @@ export const CreateRefundForm = ({
|
|||||||
<span>{payment.provider_id}</span>
|
<span>{payment.provider_id}</span>
|
||||||
<span> - ({payment.id.replace("pay_", "")})</span>
|
<span> - ({payment.id.replace("pay_", "")})</span>
|
||||||
</Select.Item>
|
</Select.Item>
|
||||||
))}
|
)
|
||||||
|
})}
|
||||||
</Select.Content>
|
</Select.Content>
|
||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
@@ -154,16 +166,11 @@ export const CreateRefundForm = ({
|
|||||||
<CurrencyInput
|
<CurrencyInput
|
||||||
{...field}
|
{...field}
|
||||||
min={0}
|
min={0}
|
||||||
onChange={(e) => {
|
onValueChange={(value) => {
|
||||||
const val =
|
const fieldValue = value ? parseInt(value) : ""
|
||||||
e.target.value === ""
|
|
||||||
? null
|
|
||||||
: Number(e.target.value)
|
|
||||||
|
|
||||||
onChange(val)
|
if (fieldValue && !isNaN(fieldValue)) {
|
||||||
|
if (fieldValue < 0 || fieldValue > paymentAmount) {
|
||||||
if (val && !isNaN(val)) {
|
|
||||||
if (val < 0 || val > paymentAmount) {
|
|
||||||
form.setError(`amount`, {
|
form.setError(`amount`, {
|
||||||
type: "manual",
|
type: "manual",
|
||||||
message: t(
|
message: t(
|
||||||
@@ -175,6 +182,8 @@ export const CreateRefundForm = ({
|
|||||||
form.clearErrors(`amount`)
|
form.clearErrors(`amount`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onChange(fieldValue)
|
||||||
}}
|
}}
|
||||||
code={order.currency_code}
|
code={order.currency_code}
|
||||||
symbol={getCurrencySymbol(order.currency_code)}
|
symbol={getCurrencySymbol(order.currency_code)}
|
||||||
|
|||||||
+4
@@ -150,6 +150,10 @@ const UnfulfilledItemDisplay = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
if (order.status === "canceled") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className="divide-y p-0">
|
<Container className="divide-y p-0">
|
||||||
<div className="flex items-center justify-between px-6 py-4">
|
<div className="flex items-center justify-between px-6 py-4">
|
||||||
|
|||||||
+17
-22
@@ -1,9 +1,5 @@
|
|||||||
import { ArrowDownRightMini, DocumentText, XCircle } from "@medusajs/icons"
|
import { ArrowDownRightMini, DocumentText, XCircle } from "@medusajs/icons"
|
||||||
import {
|
import { AdminOrder, AdminPayment, HttpTypes } from "@medusajs/types"
|
||||||
AdminPayment,
|
|
||||||
AdminPaymentCollection,
|
|
||||||
HttpTypes,
|
|
||||||
} from "@medusajs/types"
|
|
||||||
import {
|
import {
|
||||||
Badge,
|
Badge,
|
||||||
Button,
|
Button,
|
||||||
@@ -58,10 +54,7 @@ export const OrderPaymentSection = ({ order }: OrderPaymentSectionProps) => {
|
|||||||
currencyCode={order.currency_code}
|
currencyCode={order.currency_code}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Total
|
<Total order={order} />
|
||||||
paymentCollections={order.payment_collections}
|
|
||||||
currencyCode={order.currency_code}
|
|
||||||
/>
|
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -195,6 +188,11 @@ const Payment = ({
|
|||||||
const showCapture =
|
const showCapture =
|
||||||
payment.captured_at === null && payment.canceled_at === null
|
payment.captured_at === null && payment.canceled_at === null
|
||||||
|
|
||||||
|
const totalRefunded = payment.refunds.reduce(
|
||||||
|
(acc, next) => next.amount + acc,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="divide-y divide-dashed">
|
<div className="divide-y divide-dashed">
|
||||||
<div className="text-ui-fg-subtle grid grid-cols-[1fr_1fr_1fr_20px] items-center gap-x-4 px-6 py-4 sm:grid-cols-[1fr_1fr_1fr_1fr_20px]">
|
<div className="text-ui-fg-subtle grid grid-cols-[1fr_1fr_1fr_20px] items-center gap-x-4 px-6 py-4 sm:grid-cols-[1fr_1fr_1fr_1fr_20px]">
|
||||||
@@ -237,7 +235,10 @@ const Payment = ({
|
|||||||
label: t("orders.payment.refund"),
|
label: t("orders.payment.refund"),
|
||||||
icon: <XCircle />,
|
icon: <XCircle />,
|
||||||
to: `/orders/${order.id}/refund?paymentId=${payment.id}`,
|
to: `/orders/${order.id}/refund?paymentId=${payment.id}`,
|
||||||
disabled: !payment.captured_at,
|
disabled:
|
||||||
|
!payment.captured_at ||
|
||||||
|
!!payment.canceled_at ||
|
||||||
|
totalRefunded >= payment.amount,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -341,15 +342,9 @@ const PaymentBreakdown = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const Total = ({
|
const Total = ({ order }: { order: AdminOrder }) => {
|
||||||
paymentCollections,
|
|
||||||
currencyCode,
|
|
||||||
}: {
|
|
||||||
paymentCollections: AdminPaymentCollection[]
|
|
||||||
currencyCode: string
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const totalPending = getTotalPending(paymentCollections)
|
const totalPending = getTotalPending(order.payment_collections)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -360,20 +355,20 @@ const Total = ({
|
|||||||
|
|
||||||
<Text size="small" weight="plus" leading="compact">
|
<Text size="small" weight="plus" leading="compact">
|
||||||
{getStylizedAmount(
|
{getStylizedAmount(
|
||||||
getTotalCaptured(paymentCollections),
|
getTotalCaptured(order.payment_collections),
|
||||||
currencyCode
|
order.currency_code
|
||||||
)}
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{totalPending > 0 && (
|
{order.status !== "canceled" && totalPending > 0 && (
|
||||||
<div className="flex items-center justify-between px-6 py-4">
|
<div className="flex items-center justify-between px-6 py-4">
|
||||||
<Text size="small" weight="plus" leading="compact">
|
<Text size="small" weight="plus" leading="compact">
|
||||||
Total pending
|
Total pending
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Text size="small" weight="plus" leading="compact">
|
<Text size="small" weight="plus" leading="compact">
|
||||||
{getStylizedAmount(totalPending, currencyCode)}
|
{getStylizedAmount(totalPending, order.currency_code)}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
+2
-1
@@ -20,6 +20,7 @@ import {
|
|||||||
AdminOrderPreview,
|
AdminOrderPreview,
|
||||||
AdminRegion,
|
AdminRegion,
|
||||||
AdminReturn,
|
AdminReturn,
|
||||||
|
AdminPaymentCollection,
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
import {
|
import {
|
||||||
Badge,
|
Badge,
|
||||||
@@ -36,7 +37,6 @@ import {
|
|||||||
} from "@medusajs/ui"
|
} from "@medusajs/ui"
|
||||||
|
|
||||||
import { AdminReservation } from "@medusajs/types/src/http"
|
import { AdminReservation } from "@medusajs/types/src/http"
|
||||||
import { AdminPaymentCollection } from "../../../../../../../../core/types/dist/http/payment/admin/entities"
|
|
||||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||||
import { Thumbnail } from "../../../../../components/common/thumbnail"
|
import { Thumbnail } from "../../../../../components/common/thumbnail"
|
||||||
import { useClaims } from "../../../../../hooks/api/claims"
|
import { useClaims } from "../../../../../hooks/api/claims"
|
||||||
@@ -309,6 +309,7 @@ const Header = ({
|
|||||||
to: `/orders/${order.id}/edits`,
|
to: `/orders/${order.id}/edits`,
|
||||||
icon: <PencilSquare />,
|
icon: <PencilSquare />,
|
||||||
disabled:
|
disabled:
|
||||||
|
order.status === "canceled" ||
|
||||||
(orderPreview?.order_change &&
|
(orderPreview?.order_change &&
|
||||||
orderPreview?.order_change?.change_type !== "edit") ||
|
orderPreview?.order_change?.change_type !== "edit") ||
|
||||||
(orderPreview?.order_change?.change_type === "edit" &&
|
(orderPreview?.order_change?.change_type === "edit" &&
|
||||||
|
|||||||
@@ -140,9 +140,8 @@ export const cancelOrderWorkflow = createWorkflow(
|
|||||||
deleteReservationsByLineItemsStep(lineItemIds),
|
deleteReservationsByLineItemsStep(lineItemIds),
|
||||||
cancelPaymentStep({ paymentIds: uncapturedPaymentIds }),
|
cancelPaymentStep({ paymentIds: uncapturedPaymentIds }),
|
||||||
refundCapturedPaymentsWorkflow.runAsStep({
|
refundCapturedPaymentsWorkflow.runAsStep({
|
||||||
input: { order_id: order.id },
|
input: { order_id: order.id, created_by: input.canceled_by },
|
||||||
}),
|
}),
|
||||||
cancelOrdersStep({ orderIds: [order.id] }),
|
|
||||||
emitEventStep({
|
emitEventStep({
|
||||||
eventName: OrderWorkflowEvents.CANCELED,
|
eventName: OrderWorkflowEvents.CANCELED,
|
||||||
data: { id: order.id },
|
data: { id: order.id },
|
||||||
@@ -162,6 +161,8 @@ export const cancelOrderWorkflow = createWorkflow(
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
cancelOrdersStep({ orderIds: [order.id] })
|
||||||
|
|
||||||
const orderCanceled = createHook("orderCanceled", {
|
const orderCanceled = createHook("orderCanceled", {
|
||||||
order,
|
order,
|
||||||
})
|
})
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@ import { createOrderChangeActionsWorkflow } from "../create-order-change-actions
|
|||||||
* This step validates that an order refund credit line can be issued
|
* This step validates that an order refund credit line can be issued
|
||||||
*/
|
*/
|
||||||
export const validateOrderRefundCreditLinesStep = createStep(
|
export const validateOrderRefundCreditLinesStep = createStep(
|
||||||
"begin-order-edit-validation",
|
"validate-order-refund-credit-lines",
|
||||||
async function ({ order }: { order: OrderDTO }) {
|
async function ({ order }: { order: OrderDTO }) {
|
||||||
throwIfOrderIsCancelled({ order })
|
throwIfOrderIsCancelled({ order })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { cancelOrderWorkflow } from "@medusajs/core-flows"
|
import { cancelOrderWorkflow } from "@medusajs/core-flows"
|
||||||
|
import {
|
||||||
|
AuthenticatedMedusaRequest,
|
||||||
|
MedusaResponse,
|
||||||
|
} from "@medusajs/framework/http"
|
||||||
import { HttpTypes } from "@medusajs/framework/types"
|
import { HttpTypes } from "@medusajs/framework/types"
|
||||||
import {
|
import {
|
||||||
ContainerRegistrationKeys,
|
ContainerRegistrationKeys,
|
||||||
remoteQueryObjectFromString,
|
remoteQueryObjectFromString,
|
||||||
} from "@medusajs/framework/utils"
|
} from "@medusajs/framework/utils"
|
||||||
import {
|
|
||||||
AuthenticatedMedusaRequest,
|
|
||||||
MedusaResponse,
|
|
||||||
} from "@medusajs/framework/http"
|
|
||||||
|
|
||||||
export const POST = async (
|
export const POST = async (
|
||||||
req: AuthenticatedMedusaRequest,
|
req: AuthenticatedMedusaRequest,
|
||||||
@@ -33,5 +33,6 @@ export const POST = async (
|
|||||||
})
|
})
|
||||||
|
|
||||||
const [order] = await remoteQuery(queryObject)
|
const [order] = await remoteQuery(queryObject)
|
||||||
|
|
||||||
res.status(200).json({ order })
|
res.status(200).json({ order })
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user