feat(core-flows,dashboard): add fixes to allow only outbound or inbound claims (#8590)
what: - allows completing claim with only inbound items - allows completing claim with only outbound items - validates against creating claims when an active change order is present
This commit is contained in:
1 parent
a45ff1c147
commit
4cb28531e5
12 files changed
+340
-54
No files matched your search
@@ -436,6 +436,7 @@ medusaIntegrationTestRunner({
|
|||||||
describe("Claims lifecycle", () => {
|
describe("Claims lifecycle", () => {
|
||||||
let claimId
|
let claimId
|
||||||
|
|
||||||
|
describe("with inbound and outbound items", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
let r2 = await api.post(
|
let r2 = await api.post(
|
||||||
"/admin/claims",
|
"/admin/claims",
|
||||||
@@ -667,7 +668,102 @@ medusaIntegrationTestRunner({
|
|||||||
)
|
)
|
||||||
).data
|
).data
|
||||||
|
|
||||||
expect(reservationsResponseAfterCanceling.reservations).toHaveLength(0)
|
expect(reservationsResponseAfterCanceling.reservations).toHaveLength(
|
||||||
|
0
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("with only outbound items", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
claimId = baseClaim.id
|
||||||
|
const item = order.items[0]
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: {
|
||||||
|
order_preview: { shipping_methods: outboundShippingMethods },
|
||||||
|
},
|
||||||
|
} = await api.post(
|
||||||
|
`/admin/claims/${claimId}/outbound/shipping-method`,
|
||||||
|
{ shipping_option_id: outboundShippingOption.id },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
const outboundShippingMethod = outboundShippingMethods.find(
|
||||||
|
(m) => m.shipping_option_id == outboundShippingOption.id
|
||||||
|
)
|
||||||
|
|
||||||
|
// Delete & recreate again to ensure it works for both delete and create
|
||||||
|
await api.delete(
|
||||||
|
`/admin/claims/${claimId}/outbound/shipping-method/${outboundShippingMethod.actions[0].id}`,
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
await api.post(
|
||||||
|
`/admin/claims/${claimId}/outbound/shipping-method`,
|
||||||
|
{ shipping_option_id: outboundShippingOption.id },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
await api.post(
|
||||||
|
`/admin/claims/${claimId}/outbound/items`,
|
||||||
|
{
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
variant_id: productExtra.variants[0].id,
|
||||||
|
quantity: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
await api.post(
|
||||||
|
`/admin/claims/${claimId}/claim-items`,
|
||||||
|
{
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: item.id,
|
||||||
|
reason: ClaimReason.PRODUCTION_FAILURE,
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should complete flow with fulfilled items successfully", async () => {
|
||||||
|
await api.post(`/admin/claims/${claimId}/request`, {}, adminHeaders)
|
||||||
|
|
||||||
|
const claim = (
|
||||||
|
await api.get(
|
||||||
|
`/admin/claims/${claimId}?fields=*claim_items,*additional_items`,
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.claim
|
||||||
|
|
||||||
|
expect(claim.additional_items).toHaveLength(1)
|
||||||
|
expect(claim.claim_items).toHaveLength(1)
|
||||||
|
expect(claim.canceled_at).toBeNull()
|
||||||
|
|
||||||
|
const fulfillOrder = (
|
||||||
|
await api.get(`/admin/orders/${order.id}`, adminHeaders)
|
||||||
|
).data.order
|
||||||
|
|
||||||
|
const fulfillableItem = fulfillOrder.items.find(
|
||||||
|
(item) => item.detail.fulfilled_quantity === 0
|
||||||
|
)
|
||||||
|
|
||||||
|
await api.post(
|
||||||
|
`/admin/orders/${order.id}/fulfillments`,
|
||||||
|
{
|
||||||
|
location_id: location.id,
|
||||||
|
items: [{ id: fulfillableItem.id, quantity: 1 }],
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -850,8 +850,9 @@
|
|||||||
"sendNotification": "Send notification",
|
"sendNotification": "Send notification",
|
||||||
"sendNotificationHint": "Notify customer about return.",
|
"sendNotificationHint": "Notify customer about return.",
|
||||||
"returnTotal": "Return total",
|
"returnTotal": "Return total",
|
||||||
|
"inboundTotal": "Inbound total",
|
||||||
"refundAmount": "Refund amount",
|
"refundAmount": "Refund amount",
|
||||||
"outstandingAmount": "Outstanding amount",
|
"outstandingAmount": "Difference amount",
|
||||||
"reason": "Reason",
|
"reason": "Reason",
|
||||||
"reasonHint": "Choose why the customer want to return items.",
|
"reasonHint": "Choose why the customer want to return items.",
|
||||||
"note": "Note",
|
"note": "Note",
|
||||||
@@ -886,7 +887,10 @@
|
|||||||
},
|
},
|
||||||
"claims": {
|
"claims": {
|
||||||
"create": "Create Claim",
|
"create": "Create Claim",
|
||||||
|
"manage": "Manage Claim",
|
||||||
"outbound": "Outbound",
|
"outbound": "Outbound",
|
||||||
|
"outboundItemAdded": "{{itemsCount}}x added through claim",
|
||||||
|
"outboundTotal": "Outbound total",
|
||||||
"outboundShipping": "Outbound shipping",
|
"outboundShipping": "Outbound shipping",
|
||||||
"outboundShippingHint": "Choose which method you want to use.",
|
"outboundShippingHint": "Choose which method you want to use.",
|
||||||
"refundAmount": "Estimated difference",
|
"refundAmount": "Estimated difference",
|
||||||
@@ -2369,8 +2373,8 @@
|
|||||||
"variants": "Variants",
|
"variants": "Variants",
|
||||||
"orders": "Orders",
|
"orders": "Orders",
|
||||||
"account": "Account",
|
"account": "Account",
|
||||||
"total": "Total",
|
"total": "Order Total",
|
||||||
"paidTotal": "Paid total",
|
"paidTotal": "Total captured",
|
||||||
"totalExclTax": "Total excl. tax",
|
"totalExclTax": "Total excl. tax",
|
||||||
"subtotal": "Subtotal",
|
"subtotal": "Subtotal",
|
||||||
"shipping": "Shipping",
|
"shipping": "Shipping",
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { AdminPaymentCollection } from "@medusajs/types"
|
||||||
|
|
||||||
|
export const getTotalCaptured = (
|
||||||
|
paymentCollections: AdminPaymentCollection[]
|
||||||
|
) =>
|
||||||
|
paymentCollections.reduce((acc, paymentCollection) => {
|
||||||
|
acc =
|
||||||
|
acc +
|
||||||
|
((paymentCollection.captured_amount as number) -
|
||||||
|
(paymentCollection.refunded_amount as number))
|
||||||
|
return acc
|
||||||
|
}, 0)
|
||||||
+53
-5
@@ -232,6 +232,12 @@ export const ClaimCreateForm = ({
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const outboundShipping = preview.shipping_methods.find((s) => {
|
||||||
|
const action = s.actions?.find((a) => a.action === "SHIPPING_ADD")
|
||||||
|
|
||||||
|
return action && !!!action?.return?.id
|
||||||
|
})
|
||||||
|
|
||||||
const {
|
const {
|
||||||
fields: inboundItems,
|
fields: inboundItems,
|
||||||
append,
|
append,
|
||||||
@@ -456,7 +462,6 @@ export const ClaimCreateForm = ({
|
|||||||
}, [preview.shipping_methods])
|
}, [preview.shipping_methods])
|
||||||
|
|
||||||
const returnTotal = preview.return_requested_total
|
const returnTotal = preview.return_requested_total
|
||||||
const refundAmount = returnTotal - shippingTotal
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RouteFocusModal.Form form={form}>
|
<RouteFocusModal.Form form={form}>
|
||||||
@@ -672,20 +677,49 @@ export const ClaimCreateForm = ({
|
|||||||
<div className="mt-8 border-y border-dotted py-4">
|
<div className="mt-8 border-y border-dotted py-4">
|
||||||
<div className="mb-2 flex items-center justify-between">
|
<div className="mb-2 flex items-center justify-between">
|
||||||
<span className="txt-small text-ui-fg-subtle">
|
<span className="txt-small text-ui-fg-subtle">
|
||||||
{t("orders.returns.returnTotal")}
|
{t("orders.returns.inboundTotal")}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span className="txt-small text-ui-fg-subtle">
|
<span className="txt-small text-ui-fg-subtle">
|
||||||
{getStylizedAmount(
|
{getStylizedAmount(
|
||||||
returnTotal ? -1 * returnTotal : returnTotal,
|
inboundPreviewItems.reduce((acc, item) => {
|
||||||
|
const action = item.actions?.find(
|
||||||
|
(act) => act.action === "RETURN_ITEM"
|
||||||
|
)
|
||||||
|
acc = acc + (action?.amount || 0)
|
||||||
|
|
||||||
|
return acc
|
||||||
|
}, 0) * -1,
|
||||||
order.currency_code
|
order.currency_code
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="mb-2 flex items-center justify-between">
|
||||||
|
<span className="txt-small text-ui-fg-subtle">
|
||||||
|
{t("orders.claims.outboundTotal")}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span className="txt-small text-ui-fg-subtle">
|
||||||
|
{getStylizedAmount(
|
||||||
|
outboundPreviewItems.reduce((acc, item) => {
|
||||||
|
const action = item.actions?.find(
|
||||||
|
(act) => act.action === "ITEM_ADD"
|
||||||
|
)
|
||||||
|
acc = acc + (action?.amount || 0)
|
||||||
|
|
||||||
|
return acc
|
||||||
|
}, 0),
|
||||||
|
order.currency_code
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-2 flex items-center justify-between">
|
||||||
<span className="txt-small text-ui-fg-subtle">
|
<span className="txt-small text-ui-fg-subtle">
|
||||||
{t("orders.returns.inboundShipping")}
|
{t("orders.returns.inboundShipping")}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span className="txt-small text-ui-fg-subtle flex items-center">
|
<span className="txt-small text-ui-fg-subtle flex items-center">
|
||||||
{!isShippingPriceEdit && (
|
{!isShippingPriceEdit && (
|
||||||
<IconButton
|
<IconButton
|
||||||
@@ -699,6 +733,7 @@ export const ClaimCreateForm = ({
|
|||||||
<PencilSquare />
|
<PencilSquare />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isShippingPriceEdit ? (
|
{isShippingPriceEdit ? (
|
||||||
<CurrencyInput
|
<CurrencyInput
|
||||||
id="js-shipping-input"
|
id="js-shipping-input"
|
||||||
@@ -750,13 +785,26 @@ export const ClaimCreateForm = ({
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="txt-small text-ui-fg-subtle">
|
||||||
|
{t("orders.claims.outboundShipping")}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span className="txt-small text-ui-fg-subtle flex items-center">
|
||||||
|
{getStylizedAmount(
|
||||||
|
outboundShipping?.amount ?? 0,
|
||||||
|
order.currency_code
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="mt-4 flex items-center justify-between border-t border-dotted pt-4">
|
<div className="mt-4 flex items-center justify-between border-t border-dotted pt-4">
|
||||||
<span className="txt-small font-medium">
|
<span className="txt-small font-medium">
|
||||||
{t("orders.claims.refundAmount")}
|
{t("orders.claims.refundAmount")}
|
||||||
</span>
|
</span>
|
||||||
<span className="txt-small font-medium">
|
<span className="txt-small font-medium">
|
||||||
{getStylizedAmount(
|
{getStylizedAmount(
|
||||||
refundAmount ? -1 * refundAmount : refundAmount,
|
preview.summary.pending_difference,
|
||||||
order.currency_code
|
order.currency_code
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
+13
-5
@@ -169,12 +169,12 @@ export const ClaimOutboundSection = ({
|
|||||||
))
|
))
|
||||||
|
|
||||||
for (const itemToRemove of itemsToRemove) {
|
for (const itemToRemove of itemsToRemove) {
|
||||||
const actionId = previewOutboundItems
|
const action = previewOutboundItems
|
||||||
.find((i) => i.variant_id === itemToRemove)
|
.find((i) => i.variant_id === itemToRemove)
|
||||||
?.actions?.find((a) => a.action === "ITEM_ADD")?.id
|
?.actions?.find((a) => a.action === "ITEM_ADD")
|
||||||
|
|
||||||
if (actionId) {
|
if (action?.id) {
|
||||||
await removeOutboundItem(actionId, {
|
await removeOutboundItem(action?.id, {
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
toast.error(error.message)
|
toast.error(error.message)
|
||||||
},
|
},
|
||||||
@@ -194,7 +194,15 @@ export const ClaimOutboundSection = ({
|
|||||||
|
|
||||||
const promises = outboundShippingMethods
|
const promises = outboundShippingMethods
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.map((action) => deleteOutboundShipping(action.id))
|
.map((outboundShippingMethod) => {
|
||||||
|
const action = outboundShippingMethod.actions?.find(
|
||||||
|
(a) => a.action === "SHIPPING_ADD"
|
||||||
|
)
|
||||||
|
|
||||||
|
if (action) {
|
||||||
|
deleteOutboundShipping(action.id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
await Promise.all(promises)
|
await Promise.all(promises)
|
||||||
|
|
||||||
|
|||||||
+9
-14
@@ -3,7 +3,7 @@ import {
|
|||||||
Payment as MedusaPayment,
|
Payment as MedusaPayment,
|
||||||
Refund as MedusaRefund,
|
Refund as MedusaRefund,
|
||||||
} from "@medusajs/medusa"
|
} from "@medusajs/medusa"
|
||||||
import { HttpTypes } from "@medusajs/types"
|
import { AdminPaymentCollection, HttpTypes } from "@medusajs/types"
|
||||||
import {
|
import {
|
||||||
Badge,
|
Badge,
|
||||||
Button,
|
Button,
|
||||||
@@ -25,6 +25,7 @@ import {
|
|||||||
getStylizedAmount,
|
getStylizedAmount,
|
||||||
} from "../../../../../lib/money-amount-helpers"
|
} from "../../../../../lib/money-amount-helpers"
|
||||||
import { getOrderPaymentStatus } from "../../../../../lib/order-helpers"
|
import { getOrderPaymentStatus } from "../../../../../lib/order-helpers"
|
||||||
|
import { getTotalCaptured } from "../../../../../lib/payment"
|
||||||
|
|
||||||
type OrderPaymentSectionProps = {
|
type OrderPaymentSectionProps = {
|
||||||
order: HttpTypes.AdminOrder
|
order: HttpTypes.AdminOrder
|
||||||
@@ -56,7 +57,10 @@ export const OrderPaymentSection = ({ order }: OrderPaymentSectionProps) => {
|
|||||||
currencyCode={order.currency_code}
|
currencyCode={order.currency_code}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Total payments={payments} currencyCode={order.currency_code} />
|
<Total
|
||||||
|
paymentCollections={order.payment_collections}
|
||||||
|
currencyCode={order.currency_code}
|
||||||
|
/>
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -310,30 +314,21 @@ const PaymentBreakdown = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Total = ({
|
const Total = ({
|
||||||
payments,
|
paymentCollections,
|
||||||
currencyCode,
|
currencyCode,
|
||||||
}: {
|
}: {
|
||||||
payments: MedusaPayment[]
|
paymentCollections: AdminPaymentCollection[]
|
||||||
currencyCode: string
|
currencyCode: string
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const refunds = payments.map((payment) => payment.refunds).flat(1)
|
|
||||||
const paid = payments.reduce((acc, payment) => acc + payment.amount, 0)
|
|
||||||
const refunded = refunds.reduce(
|
|
||||||
(acc, refund) => acc + (refund.amount || 0),
|
|
||||||
0
|
|
||||||
)
|
|
||||||
|
|
||||||
const total = paid - refunded
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<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">
|
||||||
{t("orders.payment.totalPaidByCustomer")}
|
{t("orders.payment.totalPaidByCustomer")}
|
||||||
</Text>
|
</Text>
|
||||||
<Text size="small" weight="plus" leading="compact">
|
<Text size="small" weight="plus" leading="compact">
|
||||||
{getStylizedAmount(total, currencyCode)}
|
{getStylizedAmount(getTotalCaptured(paymentCollections), currencyCode)}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
+130
-23
@@ -1,38 +1,46 @@
|
|||||||
|
import { useMemo } from "react"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import { useNavigate } from "react-router-dom"
|
import { useNavigate } from "react-router-dom"
|
||||||
import { useMemo } from "react"
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
ArrowDownRightMini,
|
||||||
|
ArrowLongRight,
|
||||||
|
ArrowUturnLeft,
|
||||||
|
DocumentText,
|
||||||
|
ExclamationCircle,
|
||||||
|
} from "@medusajs/icons"
|
||||||
|
import {
|
||||||
|
AdminClaim,
|
||||||
AdminOrder,
|
AdminOrder,
|
||||||
|
AdminOrderPreview,
|
||||||
AdminReturn,
|
AdminReturn,
|
||||||
OrderLineItemDTO,
|
OrderLineItemDTO,
|
||||||
ReservationItemDTO,
|
ReservationItemDTO,
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
import {
|
import {
|
||||||
ArrowDownRightMini,
|
Badge,
|
||||||
ArrowUturnLeft,
|
|
||||||
ExclamationCircle,
|
|
||||||
ArrowLongRight,
|
|
||||||
} from "@medusajs/icons"
|
|
||||||
import {
|
|
||||||
Button,
|
Button,
|
||||||
Container,
|
Container,
|
||||||
Copy,
|
Copy,
|
||||||
Heading,
|
Heading,
|
||||||
StatusBadge,
|
StatusBadge,
|
||||||
Text,
|
Text,
|
||||||
|
Tooltip,
|
||||||
} from "@medusajs/ui"
|
} from "@medusajs/ui"
|
||||||
|
|
||||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||||
|
import { ButtonMenu } from "../../../../../components/common/button-menu/button-menu.tsx"
|
||||||
import { Thumbnail } from "../../../../../components/common/thumbnail"
|
import { Thumbnail } from "../../../../../components/common/thumbnail"
|
||||||
|
import { useClaims } from "../../../../../hooks/api/claims.tsx"
|
||||||
|
import { useOrderPreview } from "../../../../../hooks/api/orders.tsx"
|
||||||
|
import { useReservationItems } from "../../../../../hooks/api/reservations"
|
||||||
|
import { useReturns } from "../../../../../hooks/api/returns"
|
||||||
|
import { useDate } from "../../../../../hooks/use-date"
|
||||||
import {
|
import {
|
||||||
getLocaleAmount,
|
getLocaleAmount,
|
||||||
getStylizedAmount,
|
getStylizedAmount,
|
||||||
} from "../../../../../lib/money-amount-helpers"
|
} from "../../../../../lib/money-amount-helpers"
|
||||||
import { useReservationItems } from "../../../../../hooks/api/reservations"
|
import { getTotalCaptured } from "../../../../../lib/payment.ts"
|
||||||
import { useReturns } from "../../../../../hooks/api/returns"
|
|
||||||
import { useDate } from "../../../../../hooks/use-date"
|
|
||||||
import { ButtonMenu } from "../../../../../components/common/button-menu/button-menu.tsx"
|
|
||||||
|
|
||||||
type OrderSummarySectionProps = {
|
type OrderSummarySectionProps = {
|
||||||
order: AdminOrder
|
order: AdminOrder
|
||||||
@@ -46,6 +54,8 @@ export const OrderSummarySection = ({ order }: OrderSummarySectionProps) => {
|
|||||||
line_item_id: order.items.map((i) => i.id),
|
line_item_id: order.items.map((i) => i.id),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const { order: orderPreview } = useOrderPreview(order.id!)
|
||||||
|
|
||||||
const { returns = [] } = useReturns({
|
const { returns = [] } = useReturns({
|
||||||
status: "requested",
|
status: "requested",
|
||||||
order_id: order.id,
|
order_id: order.id,
|
||||||
@@ -84,7 +94,7 @@ export const OrderSummarySection = ({ order }: OrderSummarySectionProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className="divide-y divide-dashed p-0">
|
<Container className="divide-y divide-dashed p-0">
|
||||||
<Header order={order} />
|
<Header order={order} orderPreview={orderPreview} />
|
||||||
<ItemBreakdown order={order} />
|
<ItemBreakdown order={order} />
|
||||||
<CostBreakdown order={order} />
|
<CostBreakdown order={order} />
|
||||||
<Total order={order} />
|
<Total order={order} />
|
||||||
@@ -125,7 +135,13 @@ export const OrderSummarySection = ({ order }: OrderSummarySectionProps) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const Header = ({ order }: { order: AdminOrder }) => {
|
const Header = ({
|
||||||
|
order,
|
||||||
|
orderPreview,
|
||||||
|
}: {
|
||||||
|
order: AdminOrder
|
||||||
|
orderPreview?: AdminOrderPreview
|
||||||
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -151,7 +167,9 @@ const Header = ({ order }: { order: AdminOrder }) => {
|
|||||||
icon: <ArrowUturnLeft />,
|
icon: <ArrowUturnLeft />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t("orders.claims.create"),
|
label: orderPreview?.order_change?.id
|
||||||
|
? t("orders.claims.manage")
|
||||||
|
: t("orders.claims.create"),
|
||||||
to: `/orders/${order.id}/claims`,
|
to: `/orders/${order.id}/claims`,
|
||||||
icon: <ExclamationCircle />,
|
icon: <ExclamationCircle />,
|
||||||
},
|
},
|
||||||
@@ -168,11 +186,13 @@ const Item = ({
|
|||||||
currencyCode,
|
currencyCode,
|
||||||
reservation,
|
reservation,
|
||||||
returns,
|
returns,
|
||||||
|
claims,
|
||||||
}: {
|
}: {
|
||||||
item: OrderLineItemDTO
|
item: OrderLineItemDTO
|
||||||
currencyCode: string
|
currencyCode: string
|
||||||
reservation?: ReservationItemDTO | null
|
reservation?: ReservationItemDTO | null
|
||||||
returns: AdminReturn[]
|
returns: AdminReturn[]
|
||||||
|
claims: AdminClaim[]
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const isInventoryManaged = item.variant.manage_inventory
|
const isInventoryManaged = item.variant.manage_inventory
|
||||||
@@ -240,6 +260,10 @@ const Item = ({
|
|||||||
{returns.map((r) => (
|
{returns.map((r) => (
|
||||||
<ReturnBreakdown key={r.id} orderReturn={r} itemId={item.id} />
|
<ReturnBreakdown key={r.id} orderReturn={r} itemId={item.id} />
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
{claims.map((claim) => (
|
||||||
|
<ClaimBreakdown key={claim.id} claim={claim} itemId={item.id} />
|
||||||
|
))}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -249,9 +273,14 @@ const ItemBreakdown = ({ order }: { order: AdminOrder }) => {
|
|||||||
line_item_id: order.items.map((i) => i.id),
|
line_item_id: order.items.map((i) => i.id),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const { claims } = useClaims({
|
||||||
|
order_id: order.id,
|
||||||
|
fields: "*additional_items",
|
||||||
|
})
|
||||||
|
|
||||||
const { returns } = useReturns({
|
const { returns } = useReturns({
|
||||||
order_id: order.id,
|
order_id: order.id,
|
||||||
fields: "*items",
|
fields: "*items,*items.reason",
|
||||||
})
|
})
|
||||||
|
|
||||||
const itemsReturnsMap = useMemo(() => {
|
const itemsReturnsMap = useMemo(() => {
|
||||||
@@ -290,6 +319,7 @@ const ItemBreakdown = ({ order }: { order: AdminOrder }) => {
|
|||||||
currencyCode={order.currency_code}
|
currencyCode={order.currency_code}
|
||||||
reservation={reservation}
|
reservation={reservation}
|
||||||
returns={itemsReturnsMap[item.id] || []}
|
returns={itemsReturnsMap[item.id] || []}
|
||||||
|
claims={claims || []}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
@@ -363,36 +393,53 @@ const ReturnBreakdown = ({
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
!["requested", "received", "partially_received"].includes(
|
!["requested", "received", "partially_received"].includes(
|
||||||
orderReturn.status
|
orderReturn.status || ""
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const isRequested = orderReturn.status === "requested"
|
const isRequested = orderReturn.status === "requested"
|
||||||
|
const item = orderReturn?.items?.find((ri) => ri.item_id === itemId)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
item && (
|
||||||
<div
|
<div
|
||||||
key={orderReturn.id}
|
key={orderReturn.id}
|
||||||
className="text-ui-fg-subtle bg-ui-bg-subtle flex flex-row justify-between gap-y-2 px-6 py-4"
|
className="txt-compact-small-plus text-ui-fg-subtle bg-ui-bg-subtle border-dotted border-t-2 border-b-2 flex flex-row justify-between gap-y-2 px-6 py-4"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<ArrowDownRightMini className="text-ui-fg-muted" />
|
<ArrowDownRightMini className="text-ui-fg-muted" />
|
||||||
<Text size="small" className="text-ui-fg-subtle">
|
<Text>
|
||||||
{t(
|
{t(
|
||||||
`orders.returns.${
|
`orders.returns.${
|
||||||
isRequested ? "returnRequestedInfo" : "returnReceivedInfo"
|
isRequested ? "returnRequestedInfo" : "returnReceivedInfo"
|
||||||
}`,
|
}`,
|
||||||
{
|
{
|
||||||
requestedItemsCount: orderReturn.items.find(
|
requestedItemsCount:
|
||||||
(ri) => ri.item_id === itemId
|
item?.[isRequested ? "quantity" : "received_quantity"],
|
||||||
)[isRequested ? "quantity" : "received_quantity"],
|
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
|
{item?.note && (
|
||||||
|
<Tooltip content={item.note}>
|
||||||
|
<DocumentText className="text-ui-tag-neutral-icon inline ml-1" />
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{item?.reason && (
|
||||||
|
<Badge
|
||||||
|
size="2xsmall"
|
||||||
|
className="cursor-default select-none capitalize"
|
||||||
|
rounded="full"
|
||||||
|
>
|
||||||
|
{item?.reason?.label}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isRequested && (
|
{orderReturn && (
|
||||||
<Text size="small" leading="compact" className="text-ui-fg-muted">
|
<Text size="small" leading="compact" className="text-ui-fg-muted">
|
||||||
{getRelativeDate(
|
{getRelativeDate(
|
||||||
isRequested ? orderReturn.created_at : orderReturn.received_at
|
isRequested ? orderReturn.created_at : orderReturn.received_at
|
||||||
@@ -401,6 +448,46 @@ const ReturnBreakdown = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ClaimBreakdown = ({
|
||||||
|
claim,
|
||||||
|
itemId,
|
||||||
|
}: {
|
||||||
|
claim: AdminClaim
|
||||||
|
itemId: string
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { getRelativeDate } = useDate()
|
||||||
|
const items = claim.additional_items.filter(
|
||||||
|
(item) => item.item?.id === itemId
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
!!items.length && (
|
||||||
|
<div
|
||||||
|
key={claim.id}
|
||||||
|
className="txt-compact-small-plus text-ui-fg-subtle bg-ui-bg-subtle border-dotted border-t-2 border-b-2 flex flex-row justify-between gap-y-2 px-6 py-4"
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<ArrowDownRightMini className="text-ui-fg-muted" />
|
||||||
|
<Text>
|
||||||
|
{t(`orders.claims.outboundItemAdded`, {
|
||||||
|
itemsCount: items.reduce(
|
||||||
|
(acc, item) => (acc = acc + item.quantity),
|
||||||
|
0
|
||||||
|
),
|
||||||
|
})}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Text size="small" leading="compact" className="text-ui-fg-muted">
|
||||||
|
{getRelativeDate(claim.created_at)}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const Total = ({ order }: { order: AdminOrder }) => {
|
const Total = ({ order }: { order: AdminOrder }) => {
|
||||||
@@ -416,12 +503,32 @@ const Total = ({ order }: { order: AdminOrder }) => {
|
|||||||
{getStylizedAmount(order.total, order.currency_code)}
|
{getStylizedAmount(order.total, order.currency_code)}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="text-ui-fg-base flex items-center justify-between">
|
<div className="text-ui-fg-base flex items-center justify-between">
|
||||||
<Text className="text-ui-fg-subtle" size="small" leading="compact">
|
<Text className="text-ui-fg-subtle" size="small" leading="compact">
|
||||||
{t("fields.paidTotal")}
|
{t("fields.paidTotal")}
|
||||||
</Text>
|
</Text>
|
||||||
<Text className="text-ui-fg-subtle" size="small" leading="compact">
|
<Text className="text-ui-fg-subtle" size="small" leading="compact">
|
||||||
{/*TODO*/}-
|
{getStylizedAmount(
|
||||||
|
getTotalCaptured(order.payment_collections || []),
|
||||||
|
order.currency_code
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-ui-fg-base flex items-center justify-between">
|
||||||
|
<Text
|
||||||
|
className="text-ui-fg-subtle text-semibold"
|
||||||
|
size="small"
|
||||||
|
leading="compact"
|
||||||
|
>
|
||||||
|
{t("orders.returns.outstandingAmount")}
|
||||||
|
</Text>
|
||||||
|
<Text className="text-ui-fg-subtle" size="small" leading="compact">
|
||||||
|
{getStylizedAmount(
|
||||||
|
order.summary.difference_sum || 0,
|
||||||
|
order.currency_code
|
||||||
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ const DEFAULT_RELATIONS = [
|
|||||||
"*items.variant.product",
|
"*items.variant.product",
|
||||||
"*items.variant.options",
|
"*items.variant.options",
|
||||||
"+items.variant.manage_inventory",
|
"+items.variant.manage_inventory",
|
||||||
|
"+summary",
|
||||||
"*shipping_address",
|
"*shipping_address",
|
||||||
"*billing_address",
|
"*billing_address",
|
||||||
"*sales_channel",
|
"*sales_channel",
|
||||||
|
|||||||
@@ -252,6 +252,9 @@ export const confirmClaimRequestWorkflow = createWorkflow(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
when({ returnId }, ({ returnId }) => {
|
||||||
|
return !!returnId
|
||||||
|
}).then(() => {
|
||||||
updateReturnsStep([
|
updateReturnsStep([
|
||||||
{
|
{
|
||||||
id: returnId,
|
id: returnId,
|
||||||
@@ -259,6 +262,7 @@ export const confirmClaimRequestWorkflow = createWorkflow(
|
|||||||
requested_at: new Date(),
|
requested_at: new Date(),
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
})
|
||||||
|
|
||||||
const claimId = transform({ createClaimItems }, ({ createClaimItems }) => {
|
const claimId = transform({ createClaimItems }, ({ createClaimItems }) => {
|
||||||
return createClaimItems?.[0]?.claim_id
|
return createClaimItems?.[0]?.claim_id
|
||||||
@@ -329,9 +333,12 @@ export const confirmClaimRequestWorkflow = createWorkflow(
|
|||||||
reserveInventoryStep(formatedInventoryItems)
|
reserveInventoryStep(formatedInventoryItems)
|
||||||
})
|
})
|
||||||
|
|
||||||
when({ returnShippingMethod }, ({ returnShippingMethod }) => {
|
when(
|
||||||
return !!returnShippingMethod
|
{ returnShippingMethod, returnId },
|
||||||
}).then(() => {
|
({ returnShippingMethod, returnId }) => {
|
||||||
|
return !!returnShippingMethod && !!returnId
|
||||||
|
}
|
||||||
|
).then(() => {
|
||||||
const returnShippingOption = useRemoteQueryStep({
|
const returnShippingOption = useRemoteQueryStep({
|
||||||
entry_point: "shipping_options",
|
entry_point: "shipping_options",
|
||||||
fields: [
|
fields: [
|
||||||
|
|||||||
@@ -56,6 +56,11 @@ export interface BasePaymentCollection {
|
|||||||
*/
|
*/
|
||||||
authorized_amount?: BigNumberValue
|
authorized_amount?: BigNumberValue
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The amount captured within the associated payment sessions.
|
||||||
|
*/
|
||||||
|
captured_amount?: BigNumberValue
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The amount refunded within the associated payments.
|
* The amount refunded within the associated payments.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -21,4 +21,6 @@ export interface BaseReturn {
|
|||||||
no_notification?: boolean
|
no_notification?: boolean
|
||||||
refund_amount?: number
|
refund_amount?: number
|
||||||
items: BaseReturnItem[]
|
items: BaseReturnItem[]
|
||||||
|
received_at: string
|
||||||
|
created_at: string
|
||||||
}
|
}
|
||||||
@@ -14,6 +14,7 @@ export const defaultAdminReturnFields = [
|
|||||||
"updated_at",
|
"updated_at",
|
||||||
"canceled_at",
|
"canceled_at",
|
||||||
"requested_at",
|
"requested_at",
|
||||||
|
"received_at",
|
||||||
]
|
]
|
||||||
|
|
||||||
export const defaultAdminDetailsReturnFields = [
|
export const defaultAdminDetailsReturnFields = [
|
||||||
|
|||||||
Reference in new issue
Block a user