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:
		
							parent
							
								
									91ecf2fabd
								
							
						
					
					
						commit
						aec793ddc7
					
				@ -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 ? (
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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 />
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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) */
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user