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:
Riqwan Thamir authored and GitHub committed 2025-01-13 16:51:38 +00:00
1 parent 7232a8a930
commit 1758bfb8d0
9 files changed
+77 -61

No files matched your search

+6
View File
@@ -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: [
@@ -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,19 +120,32 @@ 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(
<span> (acc, next) => next.amount + acc,
{getLocaleAmount( 0
payment.amount as number, )
payment.currency_code
)} return (
{" - "} <Select.Item
</span> value={payment!.id}
<span>{payment.provider_id}</span> key={payment.id}
<span> - ({payment.id.replace("pay_", "")})</span> disabled={
</Select.Item> !!payment.canceled_at || totalRefunded >= payment.amount
))} }
>
<span>
{getLocaleAmount(
payment.amount as number,
payment.currency_code
)}
{" - "}
</span>
<span>{payment.provider_id}</span>
<span> - ({payment.id.replace("pay_", "")})</span>
</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)}
@@ -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">
@@ -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>
)} )}
@@ -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,
}) })
@@ -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 })
} }