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:
Riqwan Thamir
2024-08-14 23:09:05 +02:00
committed by GitHub
parent a45ff1c147
commit 4cb28531e5
12 changed files with 579 additions and 293 deletions

View File

@@ -436,238 +436,334 @@ medusaIntegrationTestRunner({
describe("Claims lifecycle", () => {
let claimId
beforeEach(async () => {
let r2 = await api.post(
"/admin/claims",
{
order_id: order2.id,
type: ClaimType.REFUND,
},
adminHeaders
)
const claimId2 = r2.data.claim.id
const item2 = order2.items[0]
const {
data: {
order_preview: {
items: [previewItem],
describe("with inbound and outbound items", () => {
beforeEach(async () => {
let r2 = await api.post(
"/admin/claims",
{
order_id: order2.id,
type: ClaimType.REFUND,
},
},
} = await api.post(
`/admin/claims/${claimId2}/inbound/items`,
{ items: [{ id: item2.id, quantity: 1 }] },
adminHeaders
)
// Delete & recreate again to ensure it works for both delete and create
await api.delete(
`/admin/claims/${claimId2}/inbound/items/${previewItem.actions[0].id}`,
adminHeaders
)
const {
data: { return: returnData },
} = await api.post(
`/admin/claims/${claimId2}/inbound/items`,
{ items: [{ id: item2.id, quantity: 1 }] },
adminHeaders
)
await api.post(
`/admin/returns/${returnData.id}`,
{
location_id: location.id,
no_notification: true,
},
adminHeaders
)
const {
data: {
order_preview: { shipping_methods: inboundShippingMethods },
},
} = await api.post(
`/admin/claims/${claimId2}/inbound/shipping-method`,
{ shipping_option_id: returnShippingOption.id },
adminHeaders
)
const inboundShippingMethod = inboundShippingMethods.find(
(m) => m.shipping_option_id == returnShippingOption.id
)
// Delete & recreate again to ensure it works for both delete and create
await api.delete(
`/admin/claims/${claimId2}/inbound/shipping-method/${inboundShippingMethod.actions[0].id}`,
adminHeaders
)
await api.post(
`/admin/claims/${claimId2}/inbound/shipping-method`,
{ shipping_option_id: returnShippingOption.id },
adminHeaders
)
await api.post(`/admin/claims/${claimId2}/request`, {}, adminHeaders)
claimId = baseClaim.id
const item = order.items[0]
let result = await api.post(
`/admin/claims/${claimId}/inbound/items`,
{
items: [
{
id: item.id,
reason_id: returnReason.id,
quantity: 2,
},
],
},
adminHeaders
)
await api.post(
`/admin/claims/${claimId}/inbound/shipping-method`,
{ shipping_option_id: returnShippingOption.id },
adminHeaders
)
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
)
// updated the requested quantity
const updateReturnItemActionId =
result.data.order_preview.items[0].actions[0].id
result = await api.post(
`/admin/claims/${claimId}/inbound/items/${updateReturnItemActionId}`,
{
quantity: 1,
},
adminHeaders
)
// New Items
await api.post(
`/admin/claims/${claimId}/outbound/items`,
{
items: [
{
variant_id: productExtra.variants[0].id,
quantity: 2,
},
],
},
adminHeaders
)
// Claim Items
await api.post(
`/admin/claims/${claimId}/claim-items`,
{
items: [
{
id: item.id,
reason: ClaimReason.PRODUCTION_FAILURE,
quantity: 1,
},
],
},
adminHeaders
)
result = await api.post(
`/admin/claims/${claimId}/request`,
{},
adminHeaders
)
result = (
await api.get(
`/admin/claims?fields=*claim_items,*additional_items`,
adminHeaders
)
).data.claims
expect(result).toHaveLength(2)
expect(result[0].additional_items).toHaveLength(1)
expect(result[0].claim_items).toHaveLength(1)
expect(result[0].canceled_at).toBeNull()
const claimId2 = r2.data.claim.id
const item2 = order2.items[0]
const reservationsResponse = (
await api.get(
`/admin/reservations?location_id[]=${location.id}`,
const {
data: {
order_preview: {
items: [previewItem],
},
},
} = await api.post(
`/admin/claims/${claimId2}/inbound/items`,
{ items: [{ id: item2.id, quantity: 1 }] },
adminHeaders
)
).data
expect(reservationsResponse.reservations).toHaveLength(1)
// Delete & recreate again to ensure it works for both delete and create
await api.delete(
`/admin/claims/${claimId2}/inbound/items/${previewItem.actions[0].id}`,
adminHeaders
)
const {
data: { return: returnData },
} = await api.post(
`/admin/claims/${claimId2}/inbound/items`,
{ items: [{ id: item2.id, quantity: 1 }] },
adminHeaders
)
await api.post(
`/admin/returns/${returnData.id}`,
{
location_id: location.id,
no_notification: true,
},
adminHeaders
)
const {
data: {
order_preview: { shipping_methods: inboundShippingMethods },
},
} = await api.post(
`/admin/claims/${claimId2}/inbound/shipping-method`,
{ shipping_option_id: returnShippingOption.id },
adminHeaders
)
const inboundShippingMethod = inboundShippingMethods.find(
(m) => m.shipping_option_id == returnShippingOption.id
)
// Delete & recreate again to ensure it works for both delete and create
await api.delete(
`/admin/claims/${claimId2}/inbound/shipping-method/${inboundShippingMethod.actions[0].id}`,
adminHeaders
)
await api.post(
`/admin/claims/${claimId2}/inbound/shipping-method`,
{ shipping_option_id: returnShippingOption.id },
adminHeaders
)
await api.post(`/admin/claims/${claimId2}/request`, {}, adminHeaders)
claimId = baseClaim.id
const item = order.items[0]
let result = await api.post(
`/admin/claims/${claimId}/inbound/items`,
{
items: [
{
id: item.id,
reason_id: returnReason.id,
quantity: 2,
},
],
},
adminHeaders
)
await api.post(
`/admin/claims/${claimId}/inbound/shipping-method`,
{ shipping_option_id: returnShippingOption.id },
adminHeaders
)
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
)
// updated the requested quantity
const updateReturnItemActionId =
result.data.order_preview.items[0].actions[0].id
result = await api.post(
`/admin/claims/${claimId}/inbound/items/${updateReturnItemActionId}`,
{
quantity: 1,
},
adminHeaders
)
// New Items
await api.post(
`/admin/claims/${claimId}/outbound/items`,
{
items: [
{
variant_id: productExtra.variants[0].id,
quantity: 2,
},
],
},
adminHeaders
)
// Claim Items
await api.post(
`/admin/claims/${claimId}/claim-items`,
{
items: [
{
id: item.id,
reason: ClaimReason.PRODUCTION_FAILURE,
quantity: 1,
},
],
},
adminHeaders
)
result = await api.post(
`/admin/claims/${claimId}/request`,
{},
adminHeaders
)
result = (
await api.get(
`/admin/claims?fields=*claim_items,*additional_items`,
adminHeaders
)
).data.claims
expect(result).toHaveLength(2)
expect(result[0].additional_items).toHaveLength(1)
expect(result[0].claim_items).toHaveLength(1)
expect(result[0].canceled_at).toBeNull()
const reservationsResponse = (
await api.get(
`/admin/reservations?location_id[]=${location.id}`,
adminHeaders
)
).data
expect(reservationsResponse.reservations).toHaveLength(1)
})
it("should complete flow with fulfilled items successfully", async () => {
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
)
})
it("should go through cancel flow successfully", async () => {
await api.post(`/admin/claims/${claimId}/cancel`, {}, adminHeaders)
const [claim] = (
await api.get(
`/admin/claims?fields=*claim_items,*additional_items`,
adminHeaders
)
).data.claims
expect(claim.canceled_at).toBeDefined()
const reservationsResponseAfterCanceling = (
await api.get(
`/admin/reservations?location_id[]=${location.id}`,
adminHeaders
)
).data
expect(reservationsResponseAfterCanceling.reservations).toHaveLength(
0
)
})
})
it("should complete flow with fulfilled items successfully", async () => {
const fulfillOrder = (
await api.get(`/admin/orders/${order.id}`, adminHeaders)
).data.order
describe("with only outbound items", () => {
beforeEach(async () => {
claimId = baseClaim.id
const item = order.items[0]
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
)
})
it("should go through cancel flow successfully", async () => {
await api.post(`/admin/claims/${claimId}/cancel`, {}, adminHeaders)
const [claim] = (
await api.get(
`/admin/claims?fields=*claim_items,*additional_items`,
const {
data: {
order_preview: { shipping_methods: outboundShippingMethods },
},
} = await api.post(
`/admin/claims/${claimId}/outbound/shipping-method`,
{ shipping_option_id: outboundShippingOption.id },
adminHeaders
)
).data.claims
expect(claim.canceled_at).toBeDefined()
const outboundShippingMethod = outboundShippingMethods.find(
(m) => m.shipping_option_id == outboundShippingOption.id
)
const reservationsResponseAfterCanceling = (
await api.get(
`/admin/reservations?location_id[]=${location.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
)
).data
expect(reservationsResponseAfterCanceling.reservations).toHaveLength(0)
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
)
})
})
})

View File

@@ -850,8 +850,9 @@
"sendNotification": "Send notification",
"sendNotificationHint": "Notify customer about return.",
"returnTotal": "Return total",
"inboundTotal": "Inbound total",
"refundAmount": "Refund amount",
"outstandingAmount": "Outstanding amount",
"outstandingAmount": "Difference amount",
"reason": "Reason",
"reasonHint": "Choose why the customer want to return items.",
"note": "Note",
@@ -886,7 +887,10 @@
},
"claims": {
"create": "Create Claim",
"manage": "Manage Claim",
"outbound": "Outbound",
"outboundItemAdded": "{{itemsCount}}x added through claim",
"outboundTotal": "Outbound total",
"outboundShipping": "Outbound shipping",
"outboundShippingHint": "Choose which method you want to use.",
"refundAmount": "Estimated difference",
@@ -2369,8 +2373,8 @@
"variants": "Variants",
"orders": "Orders",
"account": "Account",
"total": "Total",
"paidTotal": "Paid total",
"total": "Order Total",
"paidTotal": "Total captured",
"totalExclTax": "Total excl. tax",
"subtotal": "Subtotal",
"shipping": "Shipping",

View File

@@ -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)

View File

@@ -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 {
fields: inboundItems,
append,
@@ -456,7 +462,6 @@ export const ClaimCreateForm = ({
}, [preview.shipping_methods])
const returnTotal = preview.return_requested_total
const refundAmount = returnTotal - shippingTotal
return (
<RouteFocusModal.Form form={form}>
@@ -672,20 +677,49 @@ export const ClaimCreateForm = ({
<div className="mt-8 border-y border-dotted py-4">
<div className="mb-2 flex items-center justify-between">
<span className="txt-small text-ui-fg-subtle">
{t("orders.returns.returnTotal")}
{t("orders.returns.inboundTotal")}
</span>
<span className="txt-small text-ui-fg-subtle">
{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
)}
</span>
</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">
{t("orders.returns.inboundShipping")}
</span>
<span className="txt-small text-ui-fg-subtle flex items-center">
{!isShippingPriceEdit && (
<IconButton
@@ -699,6 +733,7 @@ export const ClaimCreateForm = ({
<PencilSquare />
</IconButton>
)}
{isShippingPriceEdit ? (
<CurrencyInput
id="js-shipping-input"
@@ -750,13 +785,26 @@ export const ClaimCreateForm = ({
</span>
</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">
<span className="txt-small font-medium">
{t("orders.claims.refundAmount")}
</span>
<span className="txt-small font-medium">
{getStylizedAmount(
refundAmount ? -1 * refundAmount : refundAmount,
preview.summary.pending_difference,
order.currency_code
)}
</span>

View File

@@ -169,12 +169,12 @@ export const ClaimOutboundSection = ({
))
for (const itemToRemove of itemsToRemove) {
const actionId = previewOutboundItems
const action = previewOutboundItems
.find((i) => i.variant_id === itemToRemove)
?.actions?.find((a) => a.action === "ITEM_ADD")?.id
?.actions?.find((a) => a.action === "ITEM_ADD")
if (actionId) {
await removeOutboundItem(actionId, {
if (action?.id) {
await removeOutboundItem(action?.id, {
onError: (error) => {
toast.error(error.message)
},
@@ -194,7 +194,15 @@ export const ClaimOutboundSection = ({
const promises = outboundShippingMethods
.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)

View File

@@ -3,7 +3,7 @@ import {
Payment as MedusaPayment,
Refund as MedusaRefund,
} from "@medusajs/medusa"
import { HttpTypes } from "@medusajs/types"
import { AdminPaymentCollection, HttpTypes } from "@medusajs/types"
import {
Badge,
Button,
@@ -25,6 +25,7 @@ import {
getStylizedAmount,
} from "../../../../../lib/money-amount-helpers"
import { getOrderPaymentStatus } from "../../../../../lib/order-helpers"
import { getTotalCaptured } from "../../../../../lib/payment"
type OrderPaymentSectionProps = {
order: HttpTypes.AdminOrder
@@ -56,7 +57,10 @@ export const OrderPaymentSection = ({ order }: OrderPaymentSectionProps) => {
currencyCode={order.currency_code}
/>
<Total payments={payments} currencyCode={order.currency_code} />
<Total
paymentCollections={order.payment_collections}
currencyCode={order.currency_code}
/>
</Container>
)
}
@@ -310,30 +314,21 @@ const PaymentBreakdown = ({
}
const Total = ({
payments,
paymentCollections,
currencyCode,
}: {
payments: MedusaPayment[]
paymentCollections: AdminPaymentCollection[]
currencyCode: string
}) => {
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 (
<div className="flex items-center justify-between px-6 py-4">
<Text size="small" weight="plus" leading="compact">
{t("orders.payment.totalPaidByCustomer")}
</Text>
<Text size="small" weight="plus" leading="compact">
{getStylizedAmount(total, currencyCode)}
{getStylizedAmount(getTotalCaptured(paymentCollections), currencyCode)}
</Text>
</div>
)

View File

@@ -1,38 +1,46 @@
import { useMemo } from "react"
import { useTranslation } from "react-i18next"
import { useNavigate } from "react-router-dom"
import { useMemo } from "react"
import {
ArrowDownRightMini,
ArrowLongRight,
ArrowUturnLeft,
DocumentText,
ExclamationCircle,
} from "@medusajs/icons"
import {
AdminClaim,
AdminOrder,
AdminOrderPreview,
AdminReturn,
OrderLineItemDTO,
ReservationItemDTO,
} from "@medusajs/types"
import {
ArrowDownRightMini,
ArrowUturnLeft,
ExclamationCircle,
ArrowLongRight,
} from "@medusajs/icons"
import {
Badge,
Button,
Container,
Copy,
Heading,
StatusBadge,
Text,
Tooltip,
} from "@medusajs/ui"
import { ActionMenu } from "../../../../../components/common/action-menu"
import { ButtonMenu } from "../../../../../components/common/button-menu/button-menu.tsx"
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 {
getLocaleAmount,
getStylizedAmount,
} from "../../../../../lib/money-amount-helpers"
import { useReservationItems } from "../../../../../hooks/api/reservations"
import { useReturns } from "../../../../../hooks/api/returns"
import { useDate } from "../../../../../hooks/use-date"
import { ButtonMenu } from "../../../../../components/common/button-menu/button-menu.tsx"
import { getTotalCaptured } from "../../../../../lib/payment.ts"
type OrderSummarySectionProps = {
order: AdminOrder
@@ -46,6 +54,8 @@ export const OrderSummarySection = ({ order }: OrderSummarySectionProps) => {
line_item_id: order.items.map((i) => i.id),
})
const { order: orderPreview } = useOrderPreview(order.id!)
const { returns = [] } = useReturns({
status: "requested",
order_id: order.id,
@@ -84,7 +94,7 @@ export const OrderSummarySection = ({ order }: OrderSummarySectionProps) => {
return (
<Container className="divide-y divide-dashed p-0">
<Header order={order} />
<Header order={order} orderPreview={orderPreview} />
<ItemBreakdown order={order} />
<CostBreakdown 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()
return (
@@ -151,7 +167,9 @@ const Header = ({ order }: { order: AdminOrder }) => {
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`,
icon: <ExclamationCircle />,
},
@@ -168,11 +186,13 @@ const Item = ({
currencyCode,
reservation,
returns,
claims,
}: {
item: OrderLineItemDTO
currencyCode: string
reservation?: ReservationItemDTO | null
returns: AdminReturn[]
claims: AdminClaim[]
}) => {
const { t } = useTranslation()
const isInventoryManaged = item.variant.manage_inventory
@@ -240,6 +260,10 @@ const Item = ({
{returns.map((r) => (
<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),
})
const { claims } = useClaims({
order_id: order.id,
fields: "*additional_items",
})
const { returns } = useReturns({
order_id: order.id,
fields: "*items",
fields: "*items,*items.reason",
})
const itemsReturnsMap = useMemo(() => {
@@ -290,6 +319,7 @@ const ItemBreakdown = ({ order }: { order: AdminOrder }) => {
currencyCode={order.currency_code}
reservation={reservation}
returns={itemsReturnsMap[item.id] || []}
claims={claims || []}
/>
)
})}
@@ -363,43 +393,100 @@ const ReturnBreakdown = ({
if (
!["requested", "received", "partially_received"].includes(
orderReturn.status
orderReturn.status || ""
)
) {
return null
}
const isRequested = orderReturn.status === "requested"
const item = orderReturn?.items?.find((ri) => ri.item_id === itemId)
return (
<div
key={orderReturn.id}
className="text-ui-fg-subtle bg-ui-bg-subtle 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 size="small" className="text-ui-fg-subtle">
{t(
`orders.returns.${
isRequested ? "returnRequestedInfo" : "returnReceivedInfo"
}`,
{
requestedItemsCount: orderReturn.items.find(
(ri) => ri.item_id === itemId
)[isRequested ? "quantity" : "received_quantity"],
}
item && (
<div
key={orderReturn.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.returns.${
isRequested ? "returnRequestedInfo" : "returnReceivedInfo"
}`,
{
requestedItemsCount:
item?.[isRequested ? "quantity" : "received_quantity"],
}
)}
</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>
{orderReturn && (
<Text size="small" leading="compact" className="text-ui-fg-muted">
{getRelativeDate(
isRequested ? orderReturn.created_at : orderReturn.received_at
)}
</Text>
)}
</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>
{isRequested && (
<Text size="small" leading="compact" className="text-ui-fg-muted">
{getRelativeDate(
isRequested ? orderReturn.created_at : orderReturn.received_at
)}
</Text>
)}
</div>
)
)
}
@@ -416,12 +503,32 @@ const Total = ({ order }: { order: AdminOrder }) => {
{getStylizedAmount(order.total, order.currency_code)}
</Text>
</div>
<div className="text-ui-fg-base flex items-center justify-between">
<Text className="text-ui-fg-subtle" size="small" leading="compact">
{t("fields.paidTotal")}
</Text>
<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>
</div>
</div>

View File

@@ -24,6 +24,7 @@ const DEFAULT_RELATIONS = [
"*items.variant.product",
"*items.variant.options",
"+items.variant.manage_inventory",
"+summary",
"*shipping_address",
"*billing_address",
"*sales_channel",

View File

@@ -252,13 +252,17 @@ export const confirmClaimRequestWorkflow = createWorkflow(
}
)
updateReturnsStep([
{
id: returnId,
status: ReturnStatus.REQUESTED,
requested_at: new Date(),
},
])
when({ returnId }, ({ returnId }) => {
return !!returnId
}).then(() => {
updateReturnsStep([
{
id: returnId,
status: ReturnStatus.REQUESTED,
requested_at: new Date(),
},
])
})
const claimId = transform({ createClaimItems }, ({ createClaimItems }) => {
return createClaimItems?.[0]?.claim_id
@@ -329,9 +333,12 @@ export const confirmClaimRequestWorkflow = createWorkflow(
reserveInventoryStep(formatedInventoryItems)
})
when({ returnShippingMethod }, ({ returnShippingMethod }) => {
return !!returnShippingMethod
}).then(() => {
when(
{ returnShippingMethod, returnId },
({ returnShippingMethod, returnId }) => {
return !!returnShippingMethod && !!returnId
}
).then(() => {
const returnShippingOption = useRemoteQueryStep({
entry_point: "shipping_options",
fields: [

View File

@@ -56,6 +56,11 @@ export interface BasePaymentCollection {
*/
authorized_amount?: BigNumberValue
/**
* The amount captured within the associated payment sessions.
*/
captured_amount?: BigNumberValue
/**
* The amount refunded within the associated payments.
*/

View File

@@ -21,4 +21,6 @@ export interface BaseReturn {
no_notification?: boolean
refund_amount?: number
items: BaseReturnItem[]
received_at: string
created_at: string
}

View File

@@ -14,6 +14,7 @@ export const defaultAdminReturnFields = [
"updated_at",
"canceled_at",
"requested_at",
"received_at",
]
export const defaultAdminDetailsReturnFields = [