2025-10-14 13:46:57 +00:00
|
|
|
import { formatCurrency } from "@erp/core";
|
2025-10-14 17:57:02 +00:00
|
|
|
import { FieldDescription, FieldGroup, FieldLegend, FieldSet, Separator } from '@repo/shadcn-ui/components';
|
2025-10-19 19:04:16 +00:00
|
|
|
import { cn } from '@repo/shadcn-ui/lib/utils';
|
2025-10-14 13:46:57 +00:00
|
|
|
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-14 17:57:02 +00:00
|
|
|
<FieldSet {...props}>
|
2025-10-19 19:04:16 +00:00
|
|
|
<FieldLegend className='hidden'>
|
2025-10-14 13:46:57 +00:00
|
|
|
<ReceiptIcon className='size-6 text-muted-foreground' />{t("form_groups.totals.title")}
|
2025-10-14 17:57:02 +00:00
|
|
|
</FieldLegend>
|
2025-09-29 18:22:59 +00:00
|
|
|
|
2025-10-19 19:04:16 +00:00
|
|
|
<FieldDescription className='hidden'>{t("form_groups.totals.description")}</FieldDescription>
|
|
|
|
|
<FieldGroup className='grid grid-cols-1 border rounded-lg bg-muted/10 p-6 gap-4'>
|
2025-10-14 13:46:57 +00:00
|
|
|
|
2025-10-19 19:04:16 +00:00
|
|
|
<div className='space-y-1.5'>
|
|
|
|
|
{/* Sección: Subtotal y Descuentos */}
|
|
|
|
|
<div className="flex justify-between text-sm">
|
|
|
|
|
<span className="text-muted-foreground">Subtotal sin descuento</span>
|
|
|
|
|
<span className="font-medium tabular-nums text-muted-foreground">
|
|
|
|
|
{formatCurrency(getValues('subtotal_amount'), 2, currency_code, language_code)}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
2025-10-14 13:46:57 +00:00
|
|
|
|
2025-10-19 19:04:16 +00:00
|
|
|
<div className="flex justify-between text-sm">
|
|
|
|
|
<div className="flex items-center gap-3">
|
|
|
|
|
<span className="text-muted-foreground">Descuento global</span>
|
|
|
|
|
<PercentageInputField
|
|
|
|
|
control={control}
|
|
|
|
|
name={"discount_percentage"}
|
|
|
|
|
readOnly={readOnly}
|
|
|
|
|
inputId={"header-discount-percentage"}
|
|
|
|
|
showSuffix={true}
|
|
|
|
|
className={cn("w-20 text-right tabular-nums bg-background", "border-input border text-sm shadow-xs")}
|
|
|
|
|
/>
|
2025-09-29 18:22:59 +00:00
|
|
|
</div>
|
2025-10-19 19:04:16 +00:00
|
|
|
<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>
|
2025-10-14 13:46:57 +00:00
|
|
|
|
2025-09-29 18:22:59 +00:00
|
|
|
|
2025-10-19 19:04:16 +00:00
|
|
|
{/* Sección: Base Imponible */}
|
|
|
|
|
<div className="flex justify-between text-sm">
|
|
|
|
|
<span className="text-foreground">Base imponible</span>
|
|
|
|
|
<span className="font-medium tabular-nums">
|
2025-10-14 13:46:57 +00:00
|
|
|
{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
|
2025-10-19 19:04:16 +00:00
|
|
|
className="text-xs font-semibold text-muted-foreground uppercase tracking-wide"
|
2025-10-14 13:46:57 +00:00
|
|
|
>
|
|
|
|
|
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 (
|
|
|
|
|
|
|
|
|
|
|
2025-10-19 19:04:16 +00:00
|
|
|
<div key={`tax-group-${group}`} className="space-y-1.5 leading-3">
|
2025-10-14 13:46:57 +00:00
|
|
|
{taxesInGroup?.map((item) => {
|
|
|
|
|
const tax = taxCatalog.findByCode(item.tax_code).match(
|
|
|
|
|
(t) => t,
|
|
|
|
|
() => undefined
|
|
|
|
|
);
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
key={`${group}:${item.tax_code}`}
|
2025-10-19 19:04:16 +00:00
|
|
|
className="flex items-center justify-between text-sm"
|
2025-10-14 13:46:57 +00:00
|
|
|
>
|
2025-10-19 19:04:16 +00:00
|
|
|
<span className="text-muted-foreground text-sm">{tax?.name}</span>
|
|
|
|
|
<span className="font-medium tabular-nums text-sm text-muted-foreground">
|
2025-10-14 13:46:57 +00:00
|
|
|
{formatCurrency(
|
|
|
|
|
item.taxes_amount,
|
|
|
|
|
2,
|
|
|
|
|
currency_code,
|
|
|
|
|
language_code
|
|
|
|
|
)}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
|
2025-10-19 19:04:16 +00:00
|
|
|
<div className="flex justify-between text-sm mt-3">
|
|
|
|
|
<span className="text-foreground">Total de impuestos</span>
|
|
|
|
|
<span className="font-medium tabular-nums">
|
|
|
|
|
{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-19 19:04:16 +00:00
|
|
|
<Separator />
|
|
|
|
|
|
|
|
|
|
<div className="flex justify-between text-sm mt-3">
|
|
|
|
|
<span className="font-semibold text-foreground">Total de la factura</span>
|
|
|
|
|
<span className="font-semibold tabular-nums">
|
|
|
|
|
{formatCurrency(getValues('total_amount'), 2, currency_code, language_code)}
|
|
|
|
|
</span>
|
2025-09-29 18:22:59 +00:00
|
|
|
</div>
|
2025-10-19 19:04:16 +00:00
|
|
|
|
2025-09-29 18:22:59 +00:00
|
|
|
</FieldGroup>
|
2025-10-14 17:57:02 +00:00
|
|
|
</FieldSet >
|
2025-09-29 18:22:59 +00:00
|
|
|
);
|
|
|
|
|
};
|