2025-10-14 13:46:57 +00:00
|
|
|
import { formatCurrency } from "@erp/core";
|
2025-09-29 18:22:59 +00:00
|
|
|
import { Description, FieldGroup, Fieldset, Legend } from "@repo/rdx-ui/components";
|
2025-10-14 13:46:57 +00:00
|
|
|
import { Separator } from "@repo/shadcn-ui/components";
|
|
|
|
|
import { ReceiptIcon } from "lucide-react";
|
|
|
|
|
import { ComponentProps } from "react";
|
|
|
|
|
import { useFormContext, useWatch } from "react-hook-form";
|
|
|
|
|
import { useInvoiceContext } from "../../context";
|
2025-09-29 18:22:59 +00:00
|
|
|
import { useTranslation } from "../../i18n";
|
2025-10-12 10:43:06 +00:00
|
|
|
import { InvoiceFormData } from "../../schemas";
|
2025-10-14 13:46:57 +00:00
|
|
|
import { PercentageInputField } from "./items/percentage-input-field";
|
2025-09-29 18:22:59 +00:00
|
|
|
|
2025-10-12 10:43:06 +00:00
|
|
|
export const InvoiceTotals = (props: ComponentProps<"fieldset">) => {
|
2025-09-29 18:22:59 +00:00
|
|
|
const { t } = useTranslation();
|
2025-10-12 10:43:06 +00:00
|
|
|
const { control, getValues } = useFormContext<InvoiceFormData>();
|
2025-10-14 13:46:57 +00:00
|
|
|
const { currency_code, language_code, readOnly, taxCatalog } = useInvoiceContext();
|
|
|
|
|
|
|
|
|
|
const displayTaxes = useWatch({
|
|
|
|
|
control,
|
|
|
|
|
name: "taxes",
|
|
|
|
|
defaultValue: [],
|
|
|
|
|
});
|
|
|
|
|
|
2025-09-29 18:22:59 +00:00
|
|
|
|
|
|
|
|
return (
|
2025-10-12 10:43:06 +00:00
|
|
|
<Fieldset {...props}>
|
2025-10-14 13:46:57 +00:00
|
|
|
<Legend>
|
|
|
|
|
<ReceiptIcon className='size-6 text-muted-foreground' />{t("form_groups.totals.title")}
|
2025-09-29 18:22:59 +00:00
|
|
|
</Legend>
|
|
|
|
|
|
|
|
|
|
<Description>{t("form_groups.totals.description")}</Description>
|
|
|
|
|
<FieldGroup className='grid grid-cols-1'>
|
2025-10-14 13:46:57 +00:00
|
|
|
{/* Sección: Subtotal y Descuentos */}
|
|
|
|
|
<div className="space-y-1.5">
|
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
<span className="font-semibold text-base">Subtotal</span>
|
|
|
|
|
<span className="font-bold text-lg tabular-nums pr-4">
|
|
|
|
|
{formatCurrency(getValues('subtotal_amount'), 2, currency_code, language_code)}</span>
|
2025-09-29 18:22:59 +00:00
|
|
|
</div>
|
2025-10-14 13:46:57 +00:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<Separator />
|
|
|
|
|
|
|
|
|
|
<div className="space-y-1.5">
|
|
|
|
|
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide">Descuento global</h3>
|
2025-09-29 18:22:59 +00:00
|
|
|
|
2025-10-14 13:46:57 +00:00
|
|
|
<div className="rounded-lg bg-accent/30 p-4 space-y-2.5">
|
|
|
|
|
<div className="flex items-center justify-between gap-4">
|
|
|
|
|
<div className="flex items-center gap-3">
|
|
|
|
|
<span className='text-sm font-medium'>Descuento global</span>
|
|
|
|
|
<PercentageInputField
|
|
|
|
|
control={control}
|
|
|
|
|
name={"discount_percentage"}
|
|
|
|
|
readOnly={readOnly}
|
|
|
|
|
inputId={"header-discount-percentage"}
|
|
|
|
|
showSuffix={true}
|
|
|
|
|
className='w-20 h-9 text-right tabular-nums'
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<span className="font-medium text-destructive tabular-nums">-{formatCurrency(getValues("discount_amount"), 2, currency_code, language_code)}</span>
|
2025-09-29 18:22:59 +00:00
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-10-14 13:46:57 +00:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<Separator />
|
2025-09-29 18:22:59 +00:00
|
|
|
|
2025-10-14 13:46:57 +00:00
|
|
|
{/* Sección: Base Imponible */}
|
|
|
|
|
<div className="space-y-1.5">
|
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
<span className="font-semibold text-base">Base imponible</span>
|
|
|
|
|
<span className="font-bold text-lg tabular-nums pr-4">
|
|
|
|
|
{formatCurrency(getValues('taxable_amount'), 2, currency_code, language_code)}
|
2025-09-29 18:22:59 +00:00
|
|
|
</span>
|
|
|
|
|
</div>
|
2025-10-14 13:46:57 +00:00
|
|
|
</div>
|
2025-09-29 18:22:59 +00:00
|
|
|
|
2025-10-14 13:46:57 +00:00
|
|
|
<Separator />
|
2025-09-29 18:22:59 +00:00
|
|
|
|
2025-10-14 13:46:57 +00:00
|
|
|
{/* Sección: Impuestos */}
|
|
|
|
|
<div className="space-y-1.5">
|
|
|
|
|
<h3
|
|
|
|
|
className="text-sm font-semibold text-muted-foreground uppercase tracking-wide"
|
|
|
|
|
>
|
|
|
|
|
Impuestos y retenciones
|
|
|
|
|
</h3>
|
|
|
|
|
|
|
|
|
|
{taxCatalog.groups().map((group) => {
|
|
|
|
|
// Filtra impuestos de ese grupo
|
|
|
|
|
const taxesInGroup = displayTaxes?.filter((item) => {
|
|
|
|
|
const tax = taxCatalog.findByCode(item.tax_code).match(
|
|
|
|
|
(t) => t,
|
|
|
|
|
() => undefined
|
|
|
|
|
);
|
|
|
|
|
return tax?.group === group;
|
|
|
|
|
});
|
2025-09-29 18:22:59 +00:00
|
|
|
|
2025-10-14 13:46:57 +00:00
|
|
|
// Si el grupo no tiene impuestos, no renderiza nada
|
|
|
|
|
if (taxesInGroup?.length === 0) return null;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div key={`tax-group-${group}`} className="rounded-lg bg-accent/30 p-4 space-y-1.5">
|
|
|
|
|
{taxesInGroup?.map((item) => {
|
|
|
|
|
const tax = taxCatalog.findByCode(item.tax_code).match(
|
|
|
|
|
(t) => t,
|
|
|
|
|
() => undefined
|
|
|
|
|
);
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
key={`${group}:${item.tax_code}`}
|
|
|
|
|
className="flex items-center justify-between"
|
|
|
|
|
>
|
|
|
|
|
<span className="text-sm font-medium">{tax?.name}</span>
|
|
|
|
|
<span className="font-medium tabular-nums">
|
|
|
|
|
{formatCurrency(
|
|
|
|
|
item.taxes_amount,
|
|
|
|
|
2,
|
|
|
|
|
currency_code,
|
|
|
|
|
language_code
|
|
|
|
|
)}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div className="flex items-center justify-between text-base mt-3">
|
|
|
|
|
<span className="font-semibold text-base">Total de impuestos</span>
|
|
|
|
|
<span className="font-bold text-lg tabular-nums pr-4">{formatCurrency(getValues('taxes_amount'), 2, currency_code, language_code)}</span>
|
2025-09-29 18:22:59 +00:00
|
|
|
</div>
|
|
|
|
|
|
2025-10-14 13:46:57 +00:00
|
|
|
</div>
|
2025-09-29 18:22:59 +00:00
|
|
|
|
2025-10-14 13:46:57 +00:00
|
|
|
<Separator className='bg-foreground' />
|
|
|
|
|
<div className="space-y-1.5">
|
|
|
|
|
<div className="rounded-lg bg-primary p-4">
|
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
<span className="text-lg font-bold text-background/90">Total de la factura</span>
|
|
|
|
|
<span className="text-2xl font-bold text-background">{formatCurrency(getValues('total_amount'), 2, currency_code, language_code)}</span>
|
|
|
|
|
</div>
|
2025-09-29 18:22:59 +00:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</FieldGroup>
|
2025-10-14 13:46:57 +00:00
|
|
|
</Fieldset >
|
2025-09-29 18:22:59 +00:00
|
|
|
);
|
|
|
|
|
};
|