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:
Frane Polić authored and GitHub committed 2024-10-18 09:40:08 +00:00
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",
@@ -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(