Editor de proformas

This commit is contained in:
David Arranz 2026-06-04 11:09:24 +02:00
parent 834d90f60c
commit 6e83958e30
22 changed files with 285 additions and 89 deletions

View File

@ -1,9 +1,11 @@
import { Outlet } from "react-router-dom";
import { AppMain } from "./app-main";
export const AppFullscreenLayout = () => {
return (
<main className="h-dvh w-dvw overflow-hidden bg-background">
<AppMain className="h-dvh w-dvw">
<Outlet />
</main>
</AppMain>
);
};

View File

@ -32,7 +32,12 @@ export const PageFormHeader = ({
contentClassName,
}: PageFormHeaderProps) => {
return (
<header className={cn("sticky top-0 z-50 border-b bg-background", className)}>
<header
className={cn(
"sticky top-0 z-50 ring-1 ring-foreground/10 bg-background shadow-xs",
className
)}
>
<div
className={cn(
"flex h-14 items-center justify-between gap-6 border-b px-4 py-2 sm:px-6 md:h-18",

View File

@ -51,7 +51,7 @@ export const PageHeader = ({
<div className="min-w-0 space-y-2">
<div className="flex min-w-0 flex-wrap items-center gap-2">
<h1 className="min-w-0 truncate text-xl font-bold tracking-tight text-foreground lg:text-2xl">
<h1 className="min-w-0 truncate text-2xl font-semibold tracking-tight text-foreground xl:text-3xl font-heading">
{title}
</h1>

View File

@ -1,3 +1,5 @@
import { isNullishOrEmpty } from "@repo/rdx-utils";
import type { ProformaItemUpdateForm, ProformaLineInput } from "../entities";
/**
@ -16,8 +18,10 @@ export const mapProformaItemFormToProformaLineInputs = (
items: ProformaItemUpdateForm[],
params: MapProformaItemFormToProformaLineInputsParams
): ProformaLineInput[] => {
return items
.filter((item) => item.isValued)
return (
items
// quitar tuplas no valoradas
.filter((item) => !(isNullishOrEmpty(item.quantity) || isNullishOrEmpty(item.unitAmount)))
.map((item) => ({
quantity: item.quantity,
unitAmount: item.unitAmount,
@ -27,5 +31,6 @@ export const mapProformaItemFormToProformaLineInputs = (
taxPercentage: item.taxPercentage,
recPercentage: item.recPercentage,
}));
}))
);
};

View File

@ -14,7 +14,7 @@ export const mapProformaItemsToProformaItemsUpdateForm = (
return {
id: item.id,
position: item.position,
isValued: item.isValued,
//isValued: item.isValued, <- campo calculado en frontend, no se mapea desde la API
description: item.description ?? null,

View File

@ -41,7 +41,8 @@ export const mapProformaToProformaUpdateForm = (proforma: Proforma): ProformaUpd
taxMode: fiscalDefaults.taxMode,
taxRegimeCode: "01", //taxRegimeCode: proforma.taxRegimeCode ?? proformaDefaults.taxRegimeCode, // TODO: implementar en API
hasTaxPercentage: fiscalDefaults.defaultTaxPercentage !== null,
hasTaxPercentage:
fiscalDefaults.taxMode === "single" && fiscalDefaults.defaultTaxPercentage !== null,
taxPercentage: fiscalDefaults.defaultTaxPercentage,
hasRecPercentage: fiscalDefaults.defaultRecPercentage !== null,

View File

@ -1,3 +1,4 @@
import { isNullishOrEmpty } from "@repo/rdx-utils";
import * as React from "react";
import {
type FieldArrayWithId,
@ -16,6 +17,8 @@ import { calculateProformaLinesTotals } from "../utils/calculations/calculate-pr
export interface ProformaItemAmounts {
subtotal: number;
itemDiscountAmount: number;
subtotalTaxable: number;
taxAmount: number;
total: number;
}
@ -35,6 +38,8 @@ export interface UseUpdateProformaItemsControllerResult {
hasItems: boolean;
itemCount: number;
getItemIsValued: (index: number) => boolean;
itemErrors: ProformaItemError[];
getItemError: (index: number) => ProformaItemError | undefined;
getItemErrorMessage: (index: number) => string | undefined;
@ -96,9 +101,14 @@ export const useUpdateProformaItemsController = ({
const appendItem = React.useCallback(() => {
const nextPosition = getValues("items")?.length ?? 0;
append(buildProformaItemUpdateDefault(nextPosition), {
shouldFocus: false,
});
append(
buildProformaItemUpdateDefault(nextPosition, {
defaultTaxPercentage: getValues("taxPercentage") ?? undefined,
}),
{
shouldFocus: true,
}
);
}, [append, getValues]);
const removeItem = React.useCallback(
@ -161,23 +171,6 @@ export const useUpdateProformaItemsController = ({
[getValues, replaceItems]
);
const getItemAmounts = React.useCallback(
(index: number): ProformaItemAmounts => {
const line = mapProformaItemFormToProformaLineInputs([items[index]], {
globalDiscountPercentage: null,
})[0];
const amounts = calculateProformaLineTotal(line);
return {
subtotal: amounts.subtotalBeforeDiscounts,
itemDiscountAmount: amounts.itemDiscountAmount,
total: amounts.subtotalBeforeGlobalDiscount,
};
},
[items]
);
const insertItemAt = React.useCallback(
(index: number) => {
const currentItems = getValues("items") ?? [];
@ -223,6 +216,19 @@ export const useUpdateProformaItemsController = ({
};
}, [items]);
const itemsIsValued = React.useMemo(() => {
return items.map((item, _) => {
return !(isNullishOrEmpty(item.quantity) || isNullishOrEmpty(item.unitAmount));
});
}, [items]);
const getItemIsValued = React.useCallback(
(index: number): boolean => {
return itemsIsValued[index] ?? false;
},
[itemsIsValued]
);
const itemErrors = React.useMemo<ProformaItemError[]>(() => {
if (!Array.isArray(errors.items)) return [];
@ -241,7 +247,6 @@ export const useUpdateProformaItemsController = ({
const error = itemErrors[index];
return (
error?.isValued?.message ??
error?.quantity?.message ??
error?.unitAmount?.message ??
error?.description?.message ??
@ -251,6 +256,25 @@ export const useUpdateProformaItemsController = ({
[itemErrors]
);
const getItemAmounts = React.useCallback(
(index: number): ProformaItemAmounts => {
const line = mapProformaItemFormToProformaLineInputs([items[index]], {
globalDiscountPercentage: null,
})[0];
const amounts = calculateProformaLineTotal(line);
return {
subtotal: amounts.subtotalBeforeDiscounts,
itemDiscountAmount: amounts.itemDiscountAmount,
subtotalTaxable: amounts.subtotalTaxable,
taxAmount: amounts.taxAmount,
total: amounts.totalAmount,
};
},
[items]
);
return {
fields,
items,
@ -258,6 +282,8 @@ export const useUpdateProformaItemsController = ({
hasItems: items.length > 0,
itemCount: items.length,
getItemIsValued,
itemErrors,
getItemError,
getItemErrorMessage,

View File

@ -98,6 +98,8 @@ export const useUpdateProformaTaxController = ({
}, [taxPercentage, getValues, hasRecPercentage, setValue, taxMode]);
const enablePerLineTaxes = useCallback(() => {
// Activar impuestos por línea
setValue("taxMode", "perLine", {
shouldDirty: true,
shouldTouch: true,
@ -106,6 +108,8 @@ export const useUpdateProformaTaxController = ({
}, [setValue]);
const disablePerLineTaxes = useCallback(() => {
// Desactivar impuestos por línea
setValue("taxMode", "single", {
shouldDirty: true,
shouldTouch: true,

View File

@ -15,6 +15,9 @@ export interface ProformaLineTotal {
subtotalBeforeGlobalDiscount: number;
globalDiscountAmount: number;
totalDiscountAmount: number;
subtotalTaxable: number;
taxAmount: number;
totalAmount: number;
}
export interface ProformaTaxBreakdown {

View File

@ -1,7 +1,7 @@
export interface ProformaItemUpdateForm {
id: string;
position: number;
isValued: boolean;
//isValued: boolean; <- campo calculado en frontend, no se mapea desde la API
description: string | null;

View File

@ -17,7 +17,7 @@ import { z } from "zod/v4";
export const ProformaItemUpdateFormSchema = z.object({
id: z.string(),
position: z.number(),
isValued: z.boolean(),
//isValued: z.boolean(), <- campo calculado en frontend, no se mapea desde la API
description: z.string().nullable(),

View File

@ -63,6 +63,7 @@ export interface LineEditorProps<TLine> {
moveDownLabel: string;
removeLabel: string;
disabled?: boolean;
className?: string;
}
@ -96,6 +97,8 @@ export const LineEditor = <TLine,>({
moveDownLabel,
removeLabel,
disabled = false,
className,
}: LineEditorProps<TLine>) => {
return (
@ -104,12 +107,24 @@ export const LineEditor = <TLine,>({
<h3 className="text-lg font-semibold">{title}</h3>
<div className="flex gap-2">
<Button onClick={onAddAtStart} size="sm" type="button" variant="outline">
<Button
disabled={disabled}
onClick={onAddAtStart}
size="sm"
type="button"
variant="outline"
>
<Plus className="mr-1 h-4 w-4" />
{addAtStartLabel}
</Button>
<Button onClick={onAddAtEnd} size="sm" type="button" variant="default">
<Button
disabled={disabled}
onClick={onAddAtEnd}
size="sm"
type="button"
variant="default"
>
<Plus className="mr-1 h-4 w-4" />
{addAtEndLabel}
</Button>
@ -132,7 +147,7 @@ export const LineEditor = <TLine,>({
))}
<TableHead className="w-[50px] font-semibold text-muted-foreground text-center">
<MoreHorizontalIcon className="size-4 text-muted-foreground mx-auto" />
Acciones
</TableHead>
</TableRow>
</TableHeader>
@ -162,13 +177,13 @@ export const LineEditor = <TLine,>({
</TableCell>
))}
<TableCell className="text-muted-foreground text-sm font-medium align-top">
<DropdownMenu>
<TableCell className="text-muted-foreground text-sm font-medium align-top text-center">
<DropdownMenu disabled={disabled}>
<DropdownMenuTrigger
render={
<Button
aria-label={actionsLabel}
className="size-8 opacity-0 group-hover:opacity-100 focus:opacity-100"
className="size-8"
size="icon"
type="button"
variant="ghost"

View File

@ -24,6 +24,7 @@ interface ProformaLineEditorProps {
showLineTaxes: boolean;
getItemIsValued: (index: number) => boolean;
getItemAmounts: (index: number) => ProformaItemAmounts;
getItemErrorMessage: (index: number) => string | undefined;
@ -40,15 +41,16 @@ interface ProformaLineEditorProps {
removeItem: (index: number) => void;
currency?: string;
disabled?: boolean;
className?: string;
}
2;
export const ProformaLineEditor = ({
fields,
showLineTaxes,
getItemIsValued,
getItemAmounts,
getItemErrorMessage,
@ -65,6 +67,7 @@ export const ProformaLineEditor = ({
removeItem,
currency = "EUR",
disabled = false,
className,
}: ProformaLineEditorProps) => {
const { t } = useTranslation();
@ -109,6 +112,22 @@ export const ProformaLineEditor = ({
/>
),
},
{
id: "subtotal",
header: t("form_fields.items.subtotal.label", "Subtotal"),
headClassName: "text-right",
className: "text-right font-semibold tabular-nums pt-4",
//cell: ({ index }) => MoneyHelper.formatCurrency(getItemAmounts(index).total, 2, currency),
cell: ({ index }) => {
const isValued = getItemIsValued(index);
if (!isValued) return "";
const { subtotal } = getItemAmounts(index);
return MoneyHelper.formatCurrency(subtotal, 2, currency);
},
},
{
id: "itemDiscountPercentage",
header: t("form_fields.items.discount_percentage.label", "Dto (%)"),
@ -123,6 +142,21 @@ export const ProformaLineEditor = ({
),
},
{
id: "itemDiscountAmount",
header: t("form_fields.items.itemDiscountAmount.label", "Imp. dto."),
headClassName: "text-right",
className: "text-right font-semibold tabular-nums pt-4",
//cell: ({ index }) => MoneyHelper.formatCurrency(getItemAmounts(index).total, 2, currency),
cell: ({ index }) => {
const isValued = getItemIsValued(index);
if (!isValued) return "";
const { itemDiscountAmount } = getItemAmounts(index);
return MoneyHelper.formatCurrency(itemDiscountAmount, 2, currency);
},
},
...(showLineTaxes
? [
{
@ -142,13 +176,74 @@ export const ProformaLineEditor = ({
]
: []),
...(showLineTaxes
? [
{
id: "taxAmount",
header: t("form_fields.items.total.label", "Imp. IVA"),
headClassName: "w-[200px] text-right",
className: "text-right font-semibold tabular-nums pt-4",
cell: ({ index }: { index: number }) => {
const isValued = getItemIsValued(index);
if (!isValued) return "";
const { taxAmount } = getItemAmounts(index);
return MoneyHelper.formatCurrency(taxAmount);
},
},
]
: []),
/*{
id: "isValued",
header: t("form_fields.items.is_valued.label", "Val."),
headClassName: "text-center",
className: "w-[60px] text-center",
cell: ({ index }) => {
const isValued = getItemIsValued(index);
return isValued ? (
<span className="size-5 rounded-full bg-green-500" />
) : (
<span className="size-5 rounded-full bg-muted-foreground/30" />
);
},
},*/
...(showLineTaxes
? [
{
id: "total",
header: t("form_fields.items.total.label", "Total"),
headClassName: "w-[200px] text-right",
className: "text-right font-semibold tabular-nums pt-4",
cell: ({ index }) => MoneyHelper.formatCurrency(getItemAmounts(index).total, 2, currency),
cell: ({ index }: { index: number }) => {
const isValued = getItemIsValued(index);
if (!isValued) return "";
const { total } = getItemAmounts(index);
return MoneyHelper.formatCurrency(total, 2, currency);
},
},
]
: []),
...(showLineTaxes
? []
: [
{
id: "subtotalTaxable",
header: t("form_fields.items.total.label", "Total"),
headClassName: "w-[200px] text-right",
className: "text-right font-semibold tabular-nums pt-4",
cell: ({ index }: { index: number }) => {
const isValued = getItemIsValued(index);
if (!isValued) return "";
const { subtotalTaxable } = getItemAmounts(index);
return MoneyHelper.formatCurrency(subtotalTaxable, 2, currency);
},
},
]),
];
return (
@ -158,6 +253,7 @@ export const ProformaLineEditor = ({
addAtStartLabel={t("common.add_at_start", "Al inicio")}
className={className}
columns={columns}
disabled={disabled}
duplicateLabel={t("common.duplicate", "Duplicar")}
getLineErrorMessage={(_, index) => {
const message = getItemErrorMessage(index);
@ -179,7 +275,12 @@ export const ProformaLineEditor = ({
onRemove={removeItem}
removeLabel={t("common.remove", "Eliminar")}
renderFooter={() => (
<ProformaLineFooterEditor currency={currency} itemsTotals={itemsTotals} totals={totals} />
<ProformaLineFooterEditor
currency={currency}
disabled={disabled}
itemsTotals={itemsTotals}
totals={totals}
/>
)}
title={t("form_fields.items.title", "Líneas de detalle")}
/>
@ -188,13 +289,14 @@ export const ProformaLineEditor = ({
type ProformaLineFooterEditorProps = Pick<
ProformaLineEditorProps,
"totals" | "itemsTotals" | "currency"
"totals" | "itemsTotals" | "currency" | "disabled"
>;
export const ProformaLineFooterEditor = ({
totals,
itemsTotals,
currency,
disabled,
}: ProformaLineFooterEditorProps) => {
const { t } = useTranslation();
@ -205,6 +307,7 @@ export const ProformaLineFooterEditor = ({
<FormFieldLabel>Descuento global (%):</FormFieldLabel>
<div className="flex items-center gap-2 w-24">
<PercentageField
disabled={disabled}
maxFractionDigits={2}
minFractionDigits={0}
name={"globalDiscountPercentage"}

View File

@ -5,11 +5,7 @@ import { PercentageField } from "@repo/rdx-ui/components";
import { preventEnterKeySubmitForm } from "@repo/rdx-ui/helpers";
import { cn } from "@repo/shadcn-ui/lib/utils";
import {
ProformaUpdatePaymentEditor,
ProformaUpdateRecipientEditor,
ProformaUpdateSettingsEditor,
} from ".";
import { ProformaUpdatePaymentEditor, ProformaUpdateRecipientEditor } from ".";
import { useTranslation } from "../../../../i18n";
import type {
@ -113,7 +109,6 @@ export const ProformaUpdateEditorForm = ({
totals={totalsCtrl.totals}
/>
<ProformaUpdateSettingsEditor className="w-full" />
<ProformaUpdatePaymentEditor
className="w-full"
disabled={isSubmitting || paymentCtrl.isLoading}

View File

@ -35,10 +35,12 @@ export const ProformaUpdateItemsEditor = ({
<ProformaLineEditor
addItemAtStart={itemsCtrl.addItemAtStart}
appendItem={itemsCtrl.appendItem}
disabled={disabled}
duplicateItem={itemsCtrl.duplicateItem}
fields={itemsCtrl.fields}
getItemAmounts={itemsCtrl.getItemAmounts}
getItemErrorMessage={itemsCtrl.getItemErrorMessage}
getItemIsValued={itemsCtrl.getItemIsValued}
insertItemAfter={itemsCtrl.insertItemAfter}
insertItemBefore={itemsCtrl.insertItemBefore}
itemsTotals={itemsCtrl.totals}

View File

@ -37,7 +37,7 @@ export const ProformaUpdateRecipientEditor = ({
}: ProformaUpdateRecipientEditorProps) => {
const { t } = useTranslation();
const showActions = !(readOnly || disabled);
const showActions = !readOnly;
return (
<Card className={className}>
@ -66,9 +66,10 @@ export const ProformaUpdateRecipientEditor = ({
</CardContent>
{showActions && (
<CardFooter className="flex flex-col space-y-4">
<CardFooter className="flex gap-2 2xl:justify-between">
<Button
className="w-full justify-center"
className="justify-center"
disabled={disabled}
onClick={onCreateCustomerClick}
type="button"
variant="outline"
@ -76,12 +77,12 @@ export const ProformaUpdateRecipientEditor = ({
<PlusIcon className="mr-2 size-4" />
{t("customers.selected_customer.new_customer", "Nuevo cliente")}
</Button>
<Button
className="w-full justify-center"
className="justify-center"
disabled={disabled}
onClick={onChangeCustomerClick}
type="button"
variant={selectedCustomer ? "secondary" : "default"}
variant={selectedCustomer ? "outline" : "default"}
>
<RefreshCwIcon className="mr-2 size-4" />
{selectedCustomer

View File

@ -110,11 +110,32 @@ export const ProformaUpdateTaxEditor = ({
"form_fields.proformas.tax_regime_code.placeholder",
"Selecciona el régimen fiscal para esta proforma"
)}
readOnly={readOnly || taxCtrl.usesPerLineTax}
readOnly={readOnly}
/>
<Separator className="w-full col-start-1 col-span-full" />
<SwitchField
className="md:col-span-12 md:col-start-1 not-disabled:cursor-pointer"
disabled={disabled}
label="Mismo IVA en todas las líneas de la proforma"
name="hasTaxPercentage"
onCheckedChange={(checked) => {
checked ? taxCtrl.disablePerLineTaxes() : taxCtrl.enablePerLineTaxes();
}}
readOnly={readOnly}
/>
<SelectField
className="md:col-span-4 md:col-start-1"
className="md:col-span-4 md:col-start-1 lg:col-span-2 2xl:col-span-6"
description={
taxCtrl.usesSingleTax
? t("form_fields.proformas.default_tax_percentage.description", "")
: t(
"form_fields.proformas.default_tax_percentage.description",
"Para las lineas de detalle nuevas"
)
}
deserialize={(value) => (value === null || value === "" ? null : Number(value))}
disabled={disabled}
inputClassName="bg-background"
@ -131,23 +152,12 @@ export const ProformaUpdateTaxEditor = ({
"form_fields.proformas.default_tax_percentage.placeholder",
"Selecciona IVA"
)}
readOnly={readOnly || taxCtrl.usesPerLineTax}
readOnly={readOnly}
serialize={(value) => (typeof value === "number" ? String(value) : "")}
/>
<Separator className="w-full col-start-1 col-span-full" />
<SwitchField
checked={taxCtrl.usesSingleTax}
className="md:col-span-12 md:col-start-1 not-disabled:cursor-pointer"
disabled={disabled || readOnly}
label="Mismo IVA en todas las líneas de la proforma"
name=""
onCheckedChange={(checked) =>
checked ? taxCtrl.disablePerLineTaxes() : taxCtrl.enablePerLineTaxes()
}
/>
<SwitchField
className="md:col-span-12 md:col-start-1"
disabled={disabled}
@ -158,7 +168,7 @@ export const ProformaUpdateTaxEditor = ({
"Recargo de equivalencia"
)}
{taxCtrl.hasRecPercentage ? (
{taxCtrl.hasRecPercentage && !taxCtrl.usesPerLineTax ? (
<span className="text-sm text-muted-foreground">
{PercentageHelper.formatPercent(taxCtrl.recPercentage ?? 0)}
</span>
@ -179,7 +189,7 @@ export const ProformaUpdateTaxEditor = ({
/>
<SelectField
className="md:col-span-4 md:col-start-1"
className="md:col-span-4 md:col-start-1 lg:col-span-2 2xl:col-span-6"
deserialize={(value) => (value === null || value === "" ? null : Number(value))}
disabled={disabled || !taxCtrl.hasRetentionPercentage}
inputClassName="bg-background"
@ -196,7 +206,7 @@ export const ProformaUpdateTaxEditor = ({
"form_fields.proformas.default_tax_percentage.placeholder",
"Selecciona IVA"
)}
readOnly={readOnly || taxCtrl.usesPerLineTax}
readOnly={readOnly}
serialize={(value) => (typeof value === "number" ? String(value) : "")}
/>
</FormSectionGrid>

View File

@ -58,7 +58,7 @@ export const ProformaUpdatePage = () => {
);
return (
<div className="fixed inset-0 z-50 flex flex-col overflow-y-scroll bg-muted">
<div className="fixed inset-0 flex flex-col overflow-hidden">
<FormProvider {...updateCtrl.form}>
<UnsavedChangesProvider isDirty={updateCtrl.form.formState.isDirty}>
<ProformaUpdateHeader

View File

@ -2,11 +2,18 @@ import { UniqueID } from "@repo/rdx-ddd";
import type { ProformaItemUpdateForm } from "../entities";
export const buildProformaItemUpdateDefault = (position: number): ProformaItemUpdateForm => {
export type buildProformaItemUpdateDefaultContext = {
defaultTaxPercentage?: number;
};
export const buildProformaItemUpdateDefault = (
position: number,
context?: buildProformaItemUpdateDefaultContext
): ProformaItemUpdateForm => {
return {
id: UniqueID.generateNewID().toString(),
position,
isValued: false,
//isValued: false, <- campo calculado en frontend, no se mapea desde la API
description: null,
@ -14,7 +21,7 @@ export const buildProformaItemUpdateDefault = (position: number): ProformaItemUp
unitAmount: null,
itemDiscountPercentage: null,
ivaPercentage: null,
taxPercentage: context?.defaultTaxPercentage ?? null,
recPercentage: null,
};
};

View File

@ -31,6 +31,7 @@ export const buildProformaUpdateDefault = (): ProformaUpdateForm => {
retentionPercentage: 15,
paymentMethodId: null,
paymentTermId: null,
items: [],
};

View File

@ -166,7 +166,6 @@ const resolveSingleRecCode = (formData: ProformaUpdateForm): string | null => {
if (!formData.hasRecPercentage) {
return null;
}
return getProformaRecCode(formData.recPercentage);
};

View File

@ -10,6 +10,9 @@ export const calculateProformaLineTotal = (line?: ProformaLineInput): ProformaLi
subtotalBeforeGlobalDiscount: 0,
globalDiscountAmount: 0,
totalDiscountAmount: 0,
subtotalTaxable: 0,
taxAmount: 0,
totalAmount: 0,
};
}
@ -33,6 +36,14 @@ export const calculateProformaLineTotal = (line?: ProformaLineInput): ProformaLi
const totalDiscountAmount = itemDiscountAmount + globalDiscountAmount;
const subtotalTaxable = subtotalBeforeDiscounts - itemDiscountAmount;
const taxPercentage = toCalculationNumber(line.taxPercentage);
const taxAmount = percentageAmount(subtotalTaxable, taxPercentage);
const totalAmount = toCalculationNumber(subtotalTaxable + taxAmount);
return {
subtotalBeforeDiscounts: roundMoney(subtotalBeforeDiscounts),
@ -43,5 +54,11 @@ export const calculateProformaLineTotal = (line?: ProformaLineInput): ProformaLi
globalDiscountAmount: roundMoney(globalDiscountAmount),
totalDiscountAmount: roundMoney(totalDiscountAmount),
subtotalTaxable: roundMoney(subtotalTaxable),
taxAmount: roundMoney(taxAmount),
totalAmount: roundMoney(totalAmount),
};
};