1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-10-27 11:02:16 +01:00

feat: calculate the esimtate invoice numbers (#10823)

This commit is contained in:
Jaanus Sellin 2025-10-17 13:24:09 +03:00 committed by GitHub
parent 91ecf2fabd
commit aec793ddc7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 129 additions and 25 deletions

View File

@ -15,7 +15,10 @@ import { formatCurrency } from './formatCurrency.ts';
import { Badge } from 'component/common/Badge/Badge.tsx';
import { BillingInvoiceFooter } from './BillingInvoiceFooter/BillingInvoiceFooter.tsx';
import { StyledAmountCell, StyledSubgrid } from './BillingInvoice.styles.tsx';
import type { DetailedInvoicesSchemaInvoicesItem } from 'openapi';
import type {
DetailedInvoicesSchemaInvoicesItem,
DetailedInvoicesLineSchema,
} from 'openapi';
import { BillingInvoiceUsageRow } from './BillingInvoiceUsageRow/BillingInvoiceUsageRow.tsx';
import { BillingInvoiceMainRow } from './BillingInvoiceMainRow/BillingInvoiceMainRow.tsx';
@ -64,7 +67,7 @@ const StyledInvoiceGrid = styled('div')(({ theme }) => ({
padding: theme.spacing(0, 2, 2),
}));
const HeaderCell = styled(Typography)(({ theme }) => ({
const HeaderCell = styled('div')(({ theme }) => ({
fontSize: theme.typography.body2.fontSize,
fontWeight: theme.typography.fontWeightMedium,
color: theme.palette.text.secondary,
@ -100,6 +103,49 @@ const CardActions = styled('div')(({ theme }) => ({
padding: theme.spacing(0, 2, 2),
}));
const calculateEstimateTotals = (
status: string,
subtotal: number,
taxAmount: number,
totalAmount: number,
taxPercentage: number | undefined,
mainLines: DetailedInvoicesLineSchema[],
usageLines: DetailedInvoicesLineSchema[],
) => {
if (status !== 'estimate') {
return {
subtotal: subtotal,
taxAmount: taxAmount,
totalAmount: totalAmount,
};
}
const mainLinesTotal = mainLines.reduce(
(sum, line) => sum + (line.totalAmount || 0),
0,
);
const usageLinesTotal = usageLines.reduce((sum, line) => {
const overage =
line.consumption && line.limit
? Math.max(0, line.consumption - line.limit)
: 0;
return sum + overage * (line.unitPrice || 0);
}, 0);
const calculatedSubtotal = mainLinesTotal + usageLinesTotal;
const calculatedTaxAmount = taxPercentage
? calculatedSubtotal * (taxPercentage / 100)
: 0;
const calculatedTotalAmount = calculatedSubtotal + calculatedTaxAmount;
return {
subtotal: calculatedSubtotal,
taxAmount: calculatedTaxAmount,
totalAmount: calculatedTotalAmount,
};
};
type BillingInvoiceProps = DetailedInvoicesSchemaInvoicesItem &
Pick<ComponentProps<typeof Accordion>, 'defaultExpanded'>;
@ -111,6 +157,7 @@ export const BillingInvoice = ({
totalAmount,
subtotal,
taxAmount,
taxPercentage,
currency,
mainLines,
usageLines,
@ -130,6 +177,20 @@ export const BillingInvoice = ({
? `, ${new Date(invoiceDate).getFullYear()}`
: '';
const {
subtotal: calculatedSubtotal,
taxAmount: calculatedTaxAmount,
totalAmount: calculatedTotalAmount,
} = calculateEstimateTotals(
status,
subtotal,
taxAmount,
totalAmount,
taxPercentage,
mainLines,
usageLines,
);
return (
<StyledAccordion defaultExpanded={Boolean(defaultExpanded)}>
<HeaderRoot
@ -160,7 +221,7 @@ export const BillingInvoice = ({
<Badge color='success'>Paid</Badge>
) : null}
<Typography variant='body1' sx={{ fontWeight: 700 }}>
{formatCurrency(totalAmount, currency)}
{formatCurrency(calculatedTotalAmount, currency)}
</Typography>
</HeaderRight>
</HeaderRoot>
@ -208,6 +269,7 @@ export const BillingInvoice = ({
<BillingInvoiceUsageRow
{...line}
invoiceCurrency={currency}
invoiceStatus={status}
/>
</StyledTableRow>
))}
@ -217,10 +279,12 @@ export const BillingInvoice = ({
)}
<BillingInvoiceFooter
subTotal={subtotal}
taxAmount={taxAmount}
totalAmount={totalAmount}
subTotal={calculatedSubtotal}
taxAmount={calculatedTaxAmount}
taxPercentage={taxPercentage}
totalAmount={calculatedTotalAmount}
currency={currency}
status={status}
/>
</StyledInvoiceGrid>
{invoiceURL || invoicePDF ? (

View File

@ -29,10 +29,12 @@ const StyledTableFooterCell = styled('div', {
...(colSpan ? { gridColumn: `span ${colSpan}` } : {}),
}));
const TaxRow: FC<{ value?: number; currency?: string }> = ({
value,
currency,
}) => {
const TaxRow: FC<{
value?: number;
percentage?: number;
currency?: string;
status?: string;
}> = ({ value, percentage, currency, status }) => {
if (value === undefined) {
return (
<StyledTableFooterCell colSpan={2}>
@ -41,9 +43,13 @@ const TaxRow: FC<{ value?: number; currency?: string }> = ({
);
}
const isEstimate = status === 'estimate';
const taxLabel =
isEstimate && percentage !== undefined ? `Tax (${percentage}%)` : 'Tax';
return (
<>
<StyledTableFooterCell>Tax</StyledTableFooterCell>
<StyledTableFooterCell>{taxLabel}</StyledTableFooterCell>
<StyledTableFooterCell>
<StyledAmountCell>
{formatCurrency(value, currency)}
@ -56,15 +62,19 @@ const TaxRow: FC<{ value?: number; currency?: string }> = ({
type BillingInvoiceFooterProps = {
subTotal?: number;
taxAmount?: number;
taxPercentage?: number;
totalAmount: number;
currency?: string;
status?: string;
};
export const BillingInvoiceFooter = ({
subTotal,
taxAmount,
taxPercentage,
totalAmount,
currency,
status,
}: BillingInvoiceFooterProps) => {
return (
<StyledTableFooter>
@ -77,7 +87,12 @@ export const BillingInvoiceFooter = ({
</StyledTableFooterCell>
</StyledTableFooterRow>
<StyledTableFooterRow>
<TaxRow value={taxAmount} currency={currency} />
<TaxRow
value={taxAmount}
percentage={taxPercentage}
currency={currency}
status={status}
/>
</StyledTableFooterRow>
<StyledTableFooterRow last>
<StyledTableFooterCell>Total</StyledTableFooterCell>

View File

@ -17,6 +17,7 @@ const StyledCellWithIndicator = styled('div')(({ theme }) => ({
type BillingInvoiceUsageRowProps = DetailedInvoicesLineSchema & {
invoiceCurrency?: string;
invoiceStatus?: string;
};
export const BillingInvoiceUsageRow = ({
@ -24,36 +25,56 @@ export const BillingInvoiceUsageRow = ({
consumption,
limit,
description,
currency,
totalAmount,
unitPrice,
invoiceCurrency,
invoiceStatus,
}: BillingInvoiceUsageRowProps) => {
const percentage =
limit && limit > 0
? Math.min(100, Math.round(((consumption || 0) / limit) * 100))
: undefined;
const hasAmount = totalAmount && totalAmount > 0;
const isEstimate = invoiceStatus === 'estimate';
const overage =
isEstimate && consumption && limit
? Math.max(0, consumption - limit)
: quantity;
const includedAmount =
isEstimate && consumption && limit
? Math.min(consumption, limit)
: consumption;
const calculatedAmount =
isEstimate && unitPrice && consumption && limit
? Math.max(0, consumption - limit) * unitPrice
: totalAmount;
const hasAmount = calculatedAmount && calculatedAmount > 0;
const formatIncludedDisplay = () => {
if (includedAmount !== undefined && limit !== undefined) {
return `${formatLargeNumbers(includedAmount)}/${formatLargeNumbers(limit)}`;
}
if (includedAmount !== undefined) {
return formatLargeNumbers(includedAmount);
}
if (limit !== undefined) {
return formatLargeNumbers(limit);
}
return '';
};
return (
<>
<StyledDescriptionCell>{description}</StyledDescriptionCell>
<StyledCellWithIndicator>
<ConsumptionIndicator percentage={percentage || 0} />
<div>
{consumption !== undefined && limit !== undefined
? `${formatLargeNumbers(consumption)}/${formatLargeNumbers(limit)}`
: consumption !== undefined
? formatLargeNumbers(consumption)
: limit !== undefined
? formatLargeNumbers(limit)
: ''}
</div>
<div>{formatIncludedDisplay()}</div>
</StyledCellWithIndicator>
<div>{quantity ? formatLargeNumbers(quantity) : ''}</div>
<div>{overage ? formatLargeNumbers(overage) : ''}</div>
{hasAmount ? (
<StyledAmountCell>
{formatCurrency(totalAmount, invoiceCurrency)}
{formatCurrency(calculatedAmount, invoiceCurrency)}
</StyledAmountCell>
) : (
<div />

View File

@ -23,4 +23,6 @@ export interface DetailedInvoicesLineSchema {
startDate?: string;
/** Total amount for this line item in minor currency units */
totalAmount: number;
/** Unit price for usage line items */
unitPrice?: number;
}

View File

@ -26,6 +26,8 @@ export type DetailedInvoicesSchemaInvoicesItem = {
subtotal: number;
/** Tax amount for the invoice */
taxAmount: number;
/** Tax percentage for the invoice */
taxPercentage?: number;
/** Total amount for the invoice */
totalAmount: number;
/** Usage line items (traffic, consumption usage, overages) */