feat(core-flows): support ad hoc returns (#13598)

* feat(core-flows): support ad hoc returns

* fix: missing transform

* handle edge case

* refactor

* replace gte for gt

* cleanup

* weird bug fix

* add test

* Create quick-nails-kick.md

* stop sending empty strings

* add code to refund reason

* fix build

* fix tests

* handle code in dashboard

* fix tests

* more tests failing

* add reference and reference id to credit lieng

* rework create refund form
This commit is contained in:
William Bouchard
2025-09-30 07:38:50 -04:00
committed by GitHub
parent bd9ecd5e66
commit 087887fefb
24 changed files with 278 additions and 255 deletions

View File

@@ -18,6 +18,13 @@ export const useRefundReasonTableColumns = () => {
sortAscLabel: t("filters.sorting.alphabeticallyAsc"),
sortDescLabel: t("filters.sorting.alphabeticallyDesc"),
}),
columnHelper.accessor("code", {
header: () => t("fields.code"),
enableSorting: true,
sortLabel: t("fields.code"),
sortAscLabel: t("filters.sorting.alphabeticallyAsc"),
sortDescLabel: t("filters.sorting.alphabeticallyDesc"),
}),
columnHelper.accessor("description", {
header: () => t("fields.description"),
cell: ({ getValue }) => <DescriptionCell description={getValue()} />,

View File

@@ -10306,6 +10306,19 @@
"required": ["label", "placeholder"],
"additionalProperties": false
},
"code": {
"type": "object",
"properties": {
"label": {
"type": "string"
},
"placeholder": {
"type": "string"
}
},
"required": ["label", "placeholder"],
"additionalProperties": false
},
"description": {
"type": "object",
"properties": {
@@ -10320,7 +10333,7 @@
"additionalProperties": false
}
},
"required": ["label", "description"],
"required": ["label", "code", "description"],
"additionalProperties": false
}
},

View File

@@ -2771,6 +2771,10 @@
"label": "Label",
"placeholder": "Gesture of goodwill"
},
"code": {
"label": "Code",
"placeholder": "gesture_of_goodwill"
},
"description": {
"label": "Description",
"placeholder": "Customer had a bad shopping experience"

View File

@@ -1,14 +1,7 @@
import { zodResolver } from "@hookform/resolvers/zod"
import { HttpTypes } from "@medusajs/types"
import {
Button,
CurrencyInput,
Label,
Select,
Textarea,
toast,
} from "@medusajs/ui"
import { useEffect, useMemo, useState } from "react"
import { Button, CurrencyInput, Select, Textarea, toast } from "@medusajs/ui"
import { useMemo, useState } from "react"
import { formatValue } from "react-currency-input-field"
import { useForm } from "react-hook-form"
import { useTranslation } from "react-i18next"
@@ -20,7 +13,6 @@ import { KeyboundForm } from "../../../../../components/utilities/keybound-form"
import { useRefundPayment, useRefundReasons } from "../../../../../hooks/api"
import { currencies } from "../../../../../lib/data/currencies"
import { formatCurrency } from "../../../../../lib/format-currency"
import { formatProvider } from "../../../../../lib/format-provider"
import { getLocaleAmount } from "../../../../../lib/money-amount-helpers"
import { getPaymentsFromOrder } from "../../../../../lib/orders"
import { useDocumentDirection } from "../../../../../hooks/use-document-direction"
@@ -63,29 +55,10 @@ export const CreateRefundForm = ({ order }: CreateRefundFormProps) => {
value: paymentAmount.toFixed(currency.decimal_digits),
float: paymentAmount,
},
note: "",
refund_reason_id: "",
},
resolver: zodResolver(CreateRefundSchema),
})
useEffect(() => {
const pendingDifference = order.summary.pending_difference as number
const paymentAmount = (payment?.amount || 0) as number
const pendingAmount =
pendingDifference < 0
? Math.min(Math.abs(pendingDifference), paymentAmount)
: paymentAmount
const normalizedAmount =
pendingAmount < 0 ? pendingAmount * -1 : pendingAmount
form.setValue("amount", {
value: normalizedAmount.toFixed(currency.decimal_digits),
float: normalizedAmount,
})
}, [payment?.id || ""])
const { mutateAsync, isPending } = useRefundPayment(order.id, payment?.id!)
const handleSubmit = form.handleSubmit(async (data) => {
@@ -123,53 +96,16 @@ export const CreateRefundForm = ({ order }: CreateRefundFormProps) => {
>
<RouteDrawer.Body className="flex-1 overflow-auto">
<div className="flex flex-col gap-y-4">
<Select
dir={direction}
value={paymentId}
onValueChange={(value) => {
setPaymentId(value)
}}
>
<Label className="txt-compact-small mb-[-6px] font-sans font-medium">
{t("orders.payment.selectPaymentToRefund")}
</Label>
<Select.Trigger>
<Select.Value
placeholder={t("orders.payment.selectPaymentToRefund")}
/>
</Select.Trigger>
<Select.Content>
{payments.map((payment) => {
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
}
className="flex items-center justify-center"
>
<span>
{getLocaleAmount(
payment.amount as number,
payment.currency_code
)}
{" - "}
</span>
<span>{formatProvider(payment.provider_id)}</span>
<span> - (#{payment.id.substring(23)})</span>
</Select.Item>
)
})}
</Select.Content>
</Select>
<div className="flex items-center">
<span>
{getLocaleAmount(
payment.amount as number,
payment.currency_code
)}
</span>
<span> - </span>
<span>(#{payment.id.substring(23)})</span>
</div>
<Form.Field
control={form.control}

View File

@@ -853,10 +853,6 @@ const DiscountAndTotalBreakdown = ({
.split("-")
.join(" ")
const prettyReferenceId = creditLine.reference_id ? (
<DisplayId id={creditLine.reference_id} />
) : null
return (
<div
key={creditLine.id}
@@ -899,7 +895,7 @@ const DiscountAndTotalBreakdown = ({
leading="compact"
className="txt-small text-ui-fg-subtle capitalize"
>
({prettyReference} {prettyReferenceId})
({prettyReference})
</Text>
</div>
<div className="relative flex-1">

View File

@@ -13,6 +13,7 @@ import { useCreateRefundReason } from "../../../../../hooks/api"
const RefundReasonCreateSchema = z.object({
label: z.string().min(1),
code: z.string().min(1),
description: z.string().optional(),
})
@@ -23,11 +24,20 @@ export const RefundReasonCreateForm = () => {
const form = useForm<z.infer<typeof RefundReasonCreateSchema>>({
defaultValues: {
label: "",
code: "",
description: "",
},
resolver: zodResolver(RefundReasonCreateSchema),
})
const generateCodeFromLabel = (label: string) => {
return label
.toLowerCase()
.replace(/[^a-z0-9]/g, "_")
.replace(/_+/g, "_")
.replace(/^_|_$/g, "")
}
const { mutateAsync, isPending } = useCreateRefundReason()
const handleSubmit = form.handleSubmit(async (data) => {
@@ -81,6 +91,42 @@ export const RefundReasonCreateForm = () => {
placeholder={t(
"refundReasons.fields.label.placeholder"
)}
onChange={(e) => {
if (
!form.getFieldState("code").isTouched ||
!form.getValues("code")
) {
form.setValue(
"code",
generateCodeFromLabel(e.target.value)
)
}
field.onChange(e)
}}
/>
</Form.Control>
<Form.ErrorMessage />
</Form.Item>
)
}}
/>
</div>
<div className="grid gap-4 md:grid-cols-2">
<Form.Field
control={form.control}
name="code"
render={({ field }) => {
return (
<Form.Item>
<Form.Label>
{t("refundReasons.fields.code.label")}
</Form.Label>
<Form.Control>
<Input
{...field}
placeholder={t(
"refundReasons.fields.code.placeholder"
)}
/>
</Form.Control>
<Form.ErrorMessage />

View File

@@ -8,7 +8,7 @@ import { z } from "zod"
import { Form } from "../../../../../components/common/form"
import { RouteDrawer, useRouteModal } from "../../../../../components/modals"
import { KeyboundForm } from "../../../../../components/utilities/keybound-form"
import { useUpdateRefundReason } from "../../../../../hooks/api/refund-reasons"
import { useUpdateRefundReason } from "../../../../../hooks/api"
type RefundReasonEditFormProps = {
refundReason: HttpTypes.AdminRefundReason
@@ -16,6 +16,7 @@ type RefundReasonEditFormProps = {
const RefundReasonEditSchema = z.object({
label: z.string().min(1),
code: z.string().min(1),
description: z.string().optional(),
})
@@ -28,6 +29,7 @@ export const RefundReasonEditForm = ({
const form = useForm<z.infer<typeof RefundReasonEditSchema>>({
defaultValues: {
label: refundReason.label,
code: refundReason.code,
description: refundReason.description ?? undefined,
},
resolver: zodResolver(RefundReasonEditSchema),
@@ -78,6 +80,26 @@ export const RefundReasonEditForm = ({
)
}}
/>
<Form.Field
control={form.control}
name="code"
render={({ field }) => {
return (
<Form.Item>
<Form.Label>
{t("refundReasons.fields.code.label")}
</Form.Label>
<Form.Control>
<Input
{...field}
placeholder={t("refundReasons.fields.code.placeholder")}
/>
</Form.Control>
<Form.ErrorMessage />
</Form.Item>
)
}}
/>
<Form.Field
control={form.control}
name="description"