feat(dashboard) taxes + discount breakdown (#9586)
**What** - add tax breakdown to order details - display discount total and codes --- CLOSES CC-553
This commit is contained in:
1 parent
aa6a28ac56
commit
5b91d71318
3 files changed
+194
-33
No files matched your search
@@ -901,7 +901,16 @@
|
|||||||
"allocateItems": "Allocate items",
|
"allocateItems": "Allocate items",
|
||||||
"editOrder": "Edit order",
|
"editOrder": "Edit order",
|
||||||
"editOrderContinue": "Continue order edit",
|
"editOrderContinue": "Continue order edit",
|
||||||
"inventoryKit": "Consists of {{count}}x inventory items"
|
"inventoryKit": "Consists of {{count}}x inventory items",
|
||||||
|
"itemTotal": "Item Total",
|
||||||
|
"shippingTotal": "Shipping Total",
|
||||||
|
"discountTotal": "Discount Total",
|
||||||
|
"taxTotalIncl": "Tax Total (included)",
|
||||||
|
"itemSubtotal": "Item Subtotal",
|
||||||
|
"shippingSubtotal": "Shipping Subtotal",
|
||||||
|
"discountSubtotal": "Discount Subtotal",
|
||||||
|
"taxTotal": "Tax Total"
|
||||||
|
|
||||||
},
|
},
|
||||||
"payment": {
|
"payment": {
|
||||||
"title": "Payments",
|
"title": "Payments",
|
||||||
|
|||||||
+179
-31
@@ -18,6 +18,7 @@ import {
|
|||||||
AdminOrder,
|
AdminOrder,
|
||||||
AdminOrderLineItem,
|
AdminOrderLineItem,
|
||||||
AdminOrderPreview,
|
AdminOrderPreview,
|
||||||
|
AdminRegion,
|
||||||
AdminReturn,
|
AdminReturn,
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
import {
|
import {
|
||||||
@@ -31,6 +32,7 @@ import {
|
|||||||
toast,
|
toast,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
usePrompt,
|
usePrompt,
|
||||||
|
clx,
|
||||||
} from "@medusajs/ui"
|
} from "@medusajs/ui"
|
||||||
|
|
||||||
import { AdminPaymentCollection } from "../../../../../../../../core/types/dist/http/payment/admin/entities"
|
import { AdminPaymentCollection } from "../../../../../../../../core/types/dist/http/payment/admin/entities"
|
||||||
@@ -528,9 +530,9 @@ const Cost = ({
|
|||||||
secondaryValue,
|
secondaryValue,
|
||||||
tooltip,
|
tooltip,
|
||||||
}: {
|
}: {
|
||||||
label: string
|
label: ReactNode
|
||||||
value: string | number
|
value: string | number
|
||||||
secondaryValue: string
|
secondaryValue?: string
|
||||||
tooltip?: ReactNode
|
tooltip?: ReactNode
|
||||||
}) => (
|
}) => (
|
||||||
<div className="grid grid-cols-3 items-center">
|
<div className="grid grid-cols-3 items-center">
|
||||||
@@ -550,45 +552,191 @@ const Cost = ({
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
const CostBreakdown = ({ order }: { order: AdminOrder }) => {
|
const CostBreakdown = ({
|
||||||
|
order,
|
||||||
|
}: {
|
||||||
|
order: AdminOrder & { region: AdminRegion }
|
||||||
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const [isTaxOpen, setIsTaxOpen] = useState(false)
|
||||||
|
const [isShippingOpen, setIsShippingOpen] = useState(false)
|
||||||
|
|
||||||
|
const discountCodes = useMemo(() => {
|
||||||
|
const codes = new Set()
|
||||||
|
order.items.forEach((item) =>
|
||||||
|
item.adjustments?.forEach((adj) => {
|
||||||
|
codes.add(adj.code)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
return Array.from(codes).sort()
|
||||||
|
}, [order])
|
||||||
|
|
||||||
|
const taxCodes = useMemo(() => {
|
||||||
|
const taxCodeMap = {}
|
||||||
|
|
||||||
|
order.items.forEach((item) => {
|
||||||
|
item.tax_lines?.forEach((line) => {
|
||||||
|
taxCodeMap[line.code] = (taxCodeMap[line.code] || 0) + line.total
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
order.shipping_methods.forEach((sm) => {
|
||||||
|
sm.tax_lines?.forEach((line) => {
|
||||||
|
taxCodeMap[line.code] = (taxCodeMap[line.code] || 0) + line.total
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return taxCodeMap
|
||||||
|
}, [order])
|
||||||
|
|
||||||
|
const automaticTaxesOn = !!order.region!.automatic_taxes
|
||||||
|
const hasTaxLines = !!Object.keys(taxCodes).length
|
||||||
|
|
||||||
|
const discountTotal = automaticTaxesOn
|
||||||
|
? order.discount_total
|
||||||
|
: order.discount_subtotal
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="text-ui-fg-subtle flex flex-col gap-y-2 px-6 py-4">
|
<div className="text-ui-fg-subtle flex flex-col gap-y-2 px-6 py-4">
|
||||||
<Cost
|
<Cost
|
||||||
label={t("fields.discount")}
|
label={t(
|
||||||
// TODO: DISCOUNTS -> moved to line items now
|
automaticTaxesOn
|
||||||
// secondaryValue={
|
? "orders.summary.itemTotal"
|
||||||
// order.discounts.length > 0
|
: "orders.summary.itemSubtotal"
|
||||||
// ? order.discounts.map((d) => d.code).join(", ")
|
)}
|
||||||
// : "-"
|
value={getLocaleAmount(order.item_total, order.currency_code)}
|
||||||
// }
|
/>
|
||||||
|
<Cost
|
||||||
|
label={
|
||||||
|
<div
|
||||||
|
onClick={() => setIsShippingOpen((o) => !o)}
|
||||||
|
className="flex cursor-pointer items-center gap-1"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
{t(
|
||||||
|
automaticTaxesOn
|
||||||
|
? "orders.summary.shippingTotal"
|
||||||
|
: "orders.summary.shippingSubtotal"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<TriangleDownMini
|
||||||
|
style={{
|
||||||
|
transform: `rotate(${isShippingOpen ? 0 : -90}deg)`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
value={getLocaleAmount(
|
||||||
|
automaticTaxesOn ? order.shipping_total : order.shipping_subtotal,
|
||||||
|
order.currency_code
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{isShippingOpen && (
|
||||||
|
<div className="flex flex-col gap-1 pl-5">
|
||||||
|
{(order.shipping_methods || [])
|
||||||
|
.sort((m1, m2) =>
|
||||||
|
(m1.created_at as string).localeCompare(m2.created_at as string)
|
||||||
|
)
|
||||||
|
.map((sm, i) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={sm.id}
|
||||||
|
className="flex items-center justify-between gap-x-2"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<span className="txt-small text-ui-fg-subtle font-medium">
|
||||||
|
{sm.name}
|
||||||
|
{sm.detail.return_id &&
|
||||||
|
` (${t("fields.returnShipping")})`}{" "}
|
||||||
|
<ShippingInfoPopover key={i} shippingMethod={sm} />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="relative flex-1">
|
||||||
|
<div className="bottom-[calc(50% - 2px)] absolute h-[1px] w-full border-b border-dashed" />
|
||||||
|
</div>
|
||||||
|
<span className="txt-small text-ui-fg-muted">
|
||||||
|
{getLocaleAmount(
|
||||||
|
automaticTaxesOn ? sm.total : sm.subtotal,
|
||||||
|
order.currency_code
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Cost
|
||||||
|
label={t(
|
||||||
|
automaticTaxesOn
|
||||||
|
? "orders.summary.discountTotal"
|
||||||
|
: "orders.summary.discountSubtotal"
|
||||||
|
)}
|
||||||
|
secondaryValue={discountCodes.join(", ")}
|
||||||
value={
|
value={
|
||||||
order.discount_total > 0
|
discountTotal > 0
|
||||||
? `- ${getLocaleAmount(order.discount_total, order.currency_code)}`
|
? `- ${getLocaleAmount(discountTotal, order.currency_code)}`
|
||||||
: "-"
|
: "-"
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{(order.shipping_methods || [])
|
|
||||||
.sort((m1, m2) =>
|
<>
|
||||||
(m1.created_at as string).localeCompare(m2.created_at as string)
|
<div className="flex justify-between">
|
||||||
)
|
<div
|
||||||
.map((sm, i) => {
|
onClick={() => hasTaxLines && setIsTaxOpen((o) => !o)}
|
||||||
return (
|
className={clx("flex items-center gap-1", {
|
||||||
<div key={sm.id}>
|
"cursor-pointer": hasTaxLines,
|
||||||
<Cost
|
})}
|
||||||
label={
|
>
|
||||||
sm.detail.return_id
|
<span className="txt-small select-none">
|
||||||
? t("fields.returnShipping")
|
{t(
|
||||||
: t("fields.outboundShipping")
|
automaticTaxesOn
|
||||||
}
|
? "orders.summary.taxTotalIncl"
|
||||||
secondaryValue={sm.name}
|
: "orders.summary.taxTotal"
|
||||||
value={getLocaleAmount(sm.total, order.currency_code)}
|
)}
|
||||||
tooltip={<ShippingInfoPopover key={i} shippingMethod={sm} />}
|
</span>
|
||||||
|
{hasTaxLines && (
|
||||||
|
<TriangleDownMini
|
||||||
|
style={{
|
||||||
|
transform: `rotate(${isTaxOpen ? 0 : -90}deg)`,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
)}
|
||||||
)
|
</div>
|
||||||
})}
|
|
||||||
|
<div className="text-right">
|
||||||
|
<Text size="small" leading="compact">
|
||||||
|
{getLocaleAmount(order.tax_total, order.currency_code)}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{isTaxOpen && (
|
||||||
|
<div className="flex flex-col gap-1 pl-5">
|
||||||
|
{Object.entries(taxCodes).map(([code, total]) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={code}
|
||||||
|
className="flex items-center justify-between gap-x-2"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<span className="txt-small text-ui-fg-subtle font-medium">
|
||||||
|
{code}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="relative flex-1">
|
||||||
|
<div className="bottom-[calc(50% - 2px)] absolute h-[1px] w-full border-b border-dashed" />
|
||||||
|
</div>
|
||||||
|
<span className="txt-small text-ui-fg-muted">
|
||||||
|
{getLocaleAmount(total, order.currency_code)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,11 @@ const DEFAULT_PROPERTIES = [
|
|||||||
"currency_code",
|
"currency_code",
|
||||||
// --- TOTALS ---
|
// --- TOTALS ---
|
||||||
"total",
|
"total",
|
||||||
|
"item_total",
|
||||||
|
"shipping_subtotal",
|
||||||
"subtotal",
|
"subtotal",
|
||||||
"discounts_total",
|
"discount_total",
|
||||||
|
"discount_subtotal",
|
||||||
"shipping_total",
|
"shipping_total",
|
||||||
"shipping_tax_total",
|
"shipping_tax_total",
|
||||||
"tax_total",
|
"tax_total",
|
||||||
@@ -40,6 +43,7 @@ const DEFAULT_RELATIONS = [
|
|||||||
"*payment_collections.payments",
|
"*payment_collections.payments",
|
||||||
"*payment_collections.payments.refunds",
|
"*payment_collections.payments.refunds",
|
||||||
"*payment_collections.payments.refunds.refund_reason",
|
"*payment_collections.payments.refunds.refund_reason",
|
||||||
|
"region.automatic_taxes",
|
||||||
]
|
]
|
||||||
|
|
||||||
export const DEFAULT_FIELDS = `${DEFAULT_PROPERTIES.join(
|
export const DEFAULT_FIELDS = `${DEFAULT_PROPERTIES.join(
|
||||||
|
|||||||
Reference in new issue
Block a user