diff --git a/modules/catalogs/src/api/infrastructure/payment-methods/persistence/sequelize/repositories/sequelize-payment-method.repository.ts b/modules/catalogs/src/api/infrastructure/payment-methods/persistence/sequelize/repositories/sequelize-payment-method.repository.ts index b11ae470..39abfe6c 100644 --- a/modules/catalogs/src/api/infrastructure/payment-methods/persistence/sequelize/repositories/sequelize-payment-method.repository.ts +++ b/modules/catalogs/src/api/infrastructure/payment-methods/persistence/sequelize/repositories/sequelize-payment-method.repository.ts @@ -163,6 +163,9 @@ export class SequelizePaymentMethodRepository try { const criteriaConverter = new CriteriaToSequelizeConverter(); const query = criteriaConverter.convert(criteria, { + mappings: { + isActive: "is_active", + }, searchableFields: [], sortableFields: ["name"], enableFullText: true, @@ -170,6 +173,8 @@ export class SequelizePaymentMethodRepository strictMode: true, // fuerza error si ORDER BY no permitido }); + console.log(query?.where); + query.where = { ...query.where, company_id: companyId.toString(), diff --git a/modules/catalogs/src/api/infrastructure/payment-terms/persistence/sequelize/repositories/sequelize-payment-term.repository.ts b/modules/catalogs/src/api/infrastructure/payment-terms/persistence/sequelize/repositories/sequelize-payment-term.repository.ts index 7baf59a6..c5fb907f 100644 --- a/modules/catalogs/src/api/infrastructure/payment-terms/persistence/sequelize/repositories/sequelize-payment-term.repository.ts +++ b/modules/catalogs/src/api/infrastructure/payment-terms/persistence/sequelize/repositories/sequelize-payment-term.repository.ts @@ -117,6 +117,9 @@ export class SequelizePaymentTermRepository const criteriaConverter = new CriteriaToSequelizeConverter(); const query = criteriaConverter.convert(criteria, { searchableFields: [], + mappings: { + isActive: "is_active", + }, sortableFields: ["name"], enableFullText: true, database: this.database, diff --git a/modules/core/src/common/dto/critera.dto.ts b/modules/core/src/common/dto/critera.dto.ts index 4422c3fb..5a56147d 100644 --- a/modules/core/src/common/dto/critera.dto.ts +++ b/modules/core/src/common/dto/critera.dto.ts @@ -8,7 +8,16 @@ import { z } from "zod/v4"; export const FilterPrimitiveSchema = z.object({ // Campos mínimos ya normalizados por el conversor field: z.string(), - operator: z.string(), + operator: z.enum([ + "CONTAINS", + "NOT_CONTAINS", + "NOT_EQUALS", + "GREATER_THAN", + "GREATER_THAN_OR_EQUAL", + "LOWER_THAN", + "LOWER_THAN_OR_EQUAL", + "EQUALS", + ]), value: z.string(), }); diff --git a/modules/customer-invoices/src/web/proformas/shared/adapters/get-proforma-by-id.adapter.ts b/modules/customer-invoices/src/web/proformas/shared/adapters/get-proforma-by-id.adapter.ts index d17c6ae5..34391391 100644 --- a/modules/customer-invoices/src/web/proformas/shared/adapters/get-proforma-by-id.adapter.ts +++ b/modules/customer-invoices/src/web/proformas/shared/adapters/get-proforma-by-id.adapter.ts @@ -44,6 +44,7 @@ export const GetProformaByIdAdapter = { taxes: dto.taxes.map(mapTaxSummary), paymentMethodId: dto.payment_method?.id ?? null, + paymentTermId: dto.payment_term?.id ?? null, subtotalAmount: MoneyDTOHelper.toNumber(dto.subtotal_amount), diff --git a/modules/customer-invoices/src/web/proformas/shared/entities/proforma.entity.ts b/modules/customer-invoices/src/web/proformas/shared/entities/proforma.entity.ts index 127db1bb..47783818 100644 --- a/modules/customer-invoices/src/web/proformas/shared/entities/proforma.entity.ts +++ b/modules/customer-invoices/src/web/proformas/shared/entities/proforma.entity.ts @@ -34,6 +34,7 @@ export interface Proforma { taxes: ProformaTaxSummary[]; paymentMethodId: string | null; + paymentTermId: string | null; subtotalAmount: number; diff --git a/modules/customer-invoices/src/web/proformas/update/adapters/map-proforma-to-proforma-update-form.adapter.ts b/modules/customer-invoices/src/web/proformas/update/adapters/map-proforma-to-proforma-update-form.adapter.ts index d42e3b36..37ddbbe5 100644 --- a/modules/customer-invoices/src/web/proformas/update/adapters/map-proforma-to-proforma-update-form.adapter.ts +++ b/modules/customer-invoices/src/web/proformas/update/adapters/map-proforma-to-proforma-update-form.adapter.ts @@ -50,7 +50,8 @@ export const mapProformaToProformaUpdateForm = (proforma: Proforma): ProformaUpd hasRetentionPercentage: fiscalDefaults.defaultRetentionPercentage !== null, retentionPercentage: fiscalDefaults.defaultRetentionPercentage, - paymentMethodId: proforma.paymentMethodId ?? proformaDefaults.paymentMethodId, + paymentMethodId: proforma.paymentMethodId, + paymentTermId: proforma.paymentTermId, items: proforma.items.map(mapProformaItemsToProformaItemsUpdateForm), }; diff --git a/modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-payment-controller.ts b/modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-payment-controller.ts index 0f0003c7..b51e981c 100644 --- a/modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-payment-controller.ts +++ b/modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-payment-controller.ts @@ -9,9 +9,29 @@ import { import { useMemo } from "react"; export const useUpdateProformaPaymentController = () => { - const paymentMethodsQuery = usePaymentMethodsListQuery(); + const paymentMethodsQuery = usePaymentMethodsListQuery({ + criteria: { + filters: [ + { + field: "isActive", + operator: "EQUALS", + value: "true", + }, + ], + }, + }); - const paymentTermsQuery = usePaymentTermsListQuery(); + const paymentTermsQuery = usePaymentTermsListQuery({ + criteria: { + filters: [ + { + field: "isActive", + operator: "EQUALS", + value: "true", + }, + ], + }, + }); const paymentMethodOptions = useMemo(() => { return getPaymentMethodOptions(paymentMethodsQuery.data?.items ?? []); diff --git a/modules/customer-invoices/src/web/proformas/update/entities/proforma-calculation.entity.ts b/modules/customer-invoices/src/web/proformas/update/entities/proforma-calculation.entity.ts index 279304b8..eb4d7f67 100644 --- a/modules/customer-invoices/src/web/proformas/update/entities/proforma-calculation.entity.ts +++ b/modules/customer-invoices/src/web/proformas/update/entities/proforma-calculation.entity.ts @@ -39,8 +39,8 @@ export interface ProformaTotals { taxBreakdown: ProformaTaxBreakdown[]; taxTotal: number; - recTotal: number; + taxRecTotal: number; retentionPercentage: number | null; retentionAmount: number; diff --git a/modules/customer-invoices/src/web/proformas/update/ui/blocks/editor-sidebar.tsx b/modules/customer-invoices/src/web/proformas/update/ui/blocks/editor-sidebar.tsx new file mode 100644 index 00000000..5bd042b6 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/update/ui/blocks/editor-sidebar.tsx @@ -0,0 +1,198 @@ +"use client"; + +import type { CustomerSelectionOption } from "@erp/customers"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@repo/shadcn-ui/components"; +import { cn } from "@repo/shadcn-ui/lib/utils"; +import { CreditCard, Percent, Receipt, Settings, UserIcon } from "lucide-react"; +import { useState } from "react"; + +import { useTranslation } from "../../../../i18n"; + +import { SelectedRecipientSummary } from "./selected-recipient"; + +interface EditorSidebarProps { + disabled?: boolean; + readOnly?: boolean; + + selectedCustomer?: CustomerSelectionOption | null; + onChangeCustomerClick: () => void; + onCreateCustomerClick: () => void; + + className?: string; +} + +export const EditorSidebar = ({ + disabled = false, + readOnly = false, + + selectedCustomer, + onChangeCustomerClick, + onCreateCustomerClick, + + className, +}: EditorSidebarProps) => { + const [isTotalsExpanded, setIsTotalsExpanded] = useState(false); + const { t } = useTranslation(); + + const formatMoney = (value: number) => { + return new Intl.NumberFormat("es-ES", { + style: "currency", + currency: proforma.config.currency, + }).format(value); + }; + + const formatPercent = (value: number) => { + return new Intl.NumberFormat("es-ES", { + style: "percent", + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }).format(value / 100); + }; + + return ( +
+ {/* Secciones en acordeon */} +
+ + {/* Cliente */} + + +
+
+ +
+
+
Cliente
+ {t( + "form_groups.proformas.customer.description", + "Cliente destinatario de la proforma" + )} +
+
+
+ + + +
+ + {/* Impuestos */} + + +
+
+ +
+
+
Impuestos
+
IVA 21%
+
+
+
+ +
+ + {/* Configuracion */} + + +
+
+ +
+
+
Configuracion
+
Estado y moneda
+
+
+
+ +
+ + {/* Condiciones de pago */} + + +
+
+ +
+
+
Condiciones de pago
+
Forma y vencimiento
+
+
+
+ {""} +
+
+
+ + {/* Resumen de totales compacto - colapsable */} +
{""}
+
+ ); +}; + +interface TotalsRowProps { + label: string; + value: string; + className?: string; + small?: boolean; + variant?: "default" | "discount" | "tax"; +} + +function TotalsRow({ label, value, className, small, variant = "default" }: TotalsRowProps) { + const valueColorClass = { + default: "text-foreground", + discount: "text-amber-700 dark:text-amber-400", + tax: "text-sky-700 dark:text-sky-400", + }; + + return ( +
+ + {label} + + + {value} + +
+ ); +} + +// Version compacta del sidebar para movil (solo totales) +export function MobileTotalsBadge({ + total, + currency = "EUR", + onClick, +}: { + total: number; + currency?: string; + onClick?: () => void; +}) { + const formatMoney = (value: number) => { + return new Intl.NumberFormat("es-ES", { + style: "currency", + currency: currency, + }).format(value); + }; + + return ( + + ); +} diff --git a/modules/customer-invoices/src/web/proformas/update/ui/blocks/index.ts b/modules/customer-invoices/src/web/proformas/update/ui/blocks/index.ts index 7caf7820..126b6941 100644 --- a/modules/customer-invoices/src/web/proformas/update/ui/blocks/index.ts +++ b/modules/customer-invoices/src/web/proformas/update/ui/blocks/index.ts @@ -1,3 +1,8 @@ +export * from "./editor-sidebar"; +export * from "./line-editor"; +export * from "./new-proforma-totals-summary"; +export * from "./proforma-compact-totals"; export * from "./proforma-line-editor"; export * from "./proforma-totals-summary"; +export * from "./proforma-update-header"; export * from "./selected-recipient"; diff --git a/modules/customer-invoices/src/web/proformas/update/ui/blocks/new-proforma-totals-summary.tsx b/modules/customer-invoices/src/web/proformas/update/ui/blocks/new-proforma-totals-summary.tsx new file mode 100644 index 00000000..5255d0b9 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/update/ui/blocks/new-proforma-totals-summary.tsx @@ -0,0 +1,284 @@ +import { MoneyHelper, PercentageHelper } from "@repo/rdx-utils"; +import { Card, CardContent, Separator } from "@repo/shadcn-ui/components"; +import { cn } from "@repo/shadcn-ui/lib/utils"; +import { Percent, Receipt, Tag } from "lucide-react"; +import type { ReactNode } from "react"; + +import { useTranslation } from "../../../../i18n"; +import type { ProformaTotals } from "../../entities"; + +interface NewProformaTotalsSummaryProps { + totals: ProformaTotals; + currency?: string; + + showRec?: boolean; + showRetention?: boolean; + + globalDiscountField?: ReactNode; + + disabled?: boolean; + className?: string; + layout?: "vertical" | "horizontal"; +} + +export const NewProformaTotalsSummary = ({ + totals, + currency = "EUR", + showRec = false, + showRetention = false, + globalDiscountField, + disabled = false, + className, + layout = "vertical", +}: NewProformaTotalsSummaryProps) => { + const { t } = useTranslation(); + + const formatMoney = (value: number): string => { + return MoneyHelper.formatCurrency(value, 2, currency); + }; + + const formatPercent = (value: number): string => { + return PercentageHelper.formatPercent(value); + }; + + const retentionLabel = + totals.retentionPercentage === null + ? "Total retenciones" + : `Total retenciones ${formatPercent(totals.retentionPercentage)}`; + + const isHorizontal = layout === "horizontal"; + + return ( + + {/* Header */} +
+
+ +
+
+

Resumen de totales

+

Desglose de la factura

+
+
+ + + {/* Layout horizontal: grid de 3 columnas en pantallas grandes */} +
+ {/* Columna 1: Subtotal + Descuentos */} +
+ {/* Subtotal */} +
+ +
+ + {/* Sección Descuentos */} +
+
+ + + Descuentos + +
+
+ + + {globalDiscountField && ( +
+ + {globalDiscountField} +
+ )} + + + + + + +
+
+
+ + {/* Columna 2: Base Imponible + Impuestos */} +
+ {/* Base Imponible */} +
+ +
+ + {/* Sección Impuestos */} +
+
+ + + Impuestos + +
+
+ {totals.taxBreakdown.length > 0 ? ( + totals.taxBreakdown.map((tax) => { + const key = `${tax.taxPercentage}:${tax.recPercentage ?? "none"}`; + + return ( +
+ + + {showRec && tax.recPercentage !== null && ( + + )} +
+ ); + }) + ) : ( +

Sin impuestos aplicados

+ )} + + + + + + {showRec && ( + + )} +
+
+
+ + {/* Columna 3: Total Final (y retenciones si aplica) */} +
+ {/* Retenciones */} + { +
+ +
+ } + + {/* Spacer para empujar el total hacia abajo en horizontal */} + {/* isHorizontal &&
*/} + + {/* Total Final */} +
+
+ + Total factura + + + {formatMoney(totals.total)} + +
+
+
+
+ + + ); +}; + +interface TotalsRowProps { + label: string; + value: string; + className?: string; + description?: string; + strong?: boolean; + variant?: "default" | "discount" | "tax"; +} + +const TotalsRow = ({ + label, + value, + description, + className, + strong = false, + variant = "default", +}: TotalsRowProps) => { + const valueColorClass = { + default: strong ? "text-foreground" : "text-muted-foreground", + discount: "text-amber-700 dark:text-amber-400", + tax: "text-primary-700 dark:text-primary-400", + }; + + return ( +
+
+
+ {label} +
+ + {description &&
{description}
} +
+ +
+ {value} +
+
+ ); +}; diff --git a/modules/customer-invoices/src/web/proformas/update/ui/blocks/proforma-compact-totals.tsx b/modules/customer-invoices/src/web/proformas/update/ui/blocks/proforma-compact-totals.tsx new file mode 100644 index 00000000..2f86d27d --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/update/ui/blocks/proforma-compact-totals.tsx @@ -0,0 +1,266 @@ +import { MoneyHelper, PercentageHelper } from "@repo/rdx-utils"; +import { + Button, + Card, + CardContent, + CardFooter, + Item, + ItemContent, + ItemMedia, + ItemTitle, + Separator, + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@repo/shadcn-ui/components"; +import { cn } from "@repo/shadcn-ui/lib/utils"; +import { + ChevronDown, + ChevronUp, + CircleMinus, + LandmarkIcon, + PercentIcon, + ReceiptTextIcon, +} from "lucide-react"; +import { type ReactNode, useState } from "react"; + +import { useTranslation } from "../../../../i18n"; +import type { ProformaTotals } from "../../entities"; + +interface ProformaCompactTotalsProps { + totals: ProformaTotals; + currency?: string; + + showRec?: boolean; + showRetention?: boolean; + + globalDiscountField?: ReactNode; + + disabled?: boolean; + className?: string; +} + +export const ProformaCompactTotals = ({ + totals, + currency = "EUR", + showRec = false, + showRetention = false, + globalDiscountField, + disabled = false, + className, +}: ProformaCompactTotalsProps) => { + const { t } = useTranslation(); + const [isExpanded, setIsExpanded] = useState(false); + + const formatMoney = (value: number): string => { + return MoneyHelper.formatCurrency(value, 2, currency); + }; + + const formatPercent = (value: number): string => { + return PercentageHelper.formatPercent(value); + }; + + return ( + + {/* Panel expandible con desglose */} + + +
+
+ {/* Descuentos */} +
+
+ + Descuentos +
+
+
+ En lineas + -{formatMoney(totals.lineDiscountTotal)} +
+
+
+ Global + ({formatPercent(totals.globalDiscountPercentage)}) +
+ -{formatMoney(totals.globalDiscountAmount)} +
+ +
+ Total descuentos + + -{formatMoney(totals.totalDiscountAmount)} + +
+
+
+ + {/* Impuestos */} +
+
+ + Impuestos +
+
+ {totals.taxBreakdown.map((tax) => { + const key = `${tax.taxPercentage}:${tax.recPercentage ?? "none"}`; + + return ( +
+
+
+ Impuesto + ({formatPercent(tax.taxPercentage)}) +
+ {formatMoney(tax.taxAmount)} +
+ {showRec && tax.recPercentage !== null ? ( +
+
+ + {t("proformas.update.totals.recPercentage", "Rec. equiv.")} + + ({formatPercent(tax.recPercentage)}) +
+ {formatMoney(totals.recTotal)} +
+ ) : null} +
+ ); + })} + + +
+ Total impuestos + + {formatMoney(totals.taxRecTotal)} + +
+
+
+ + {/* Retenciones */} +
+
+ + Retenciones +
+
+ {totals.retentionPercentage ? ( + <> +
+
+ IRPF + ({formatPercent(totals.retentionPercentage)}) +
+ -{formatMoney(totals.retentionAmount)} +
+ +
+ Total retenciones + -{formatMoney(totals.retentionAmount)} +
+ + ) : ( +
Sin retenciones
+ )} +
+
+
+
+
+ + {/* Barra principal de totales - siempre visible */} + + {/* Boton expandir */} + + + {/* Totales en linea */} +
+
+ + {t("proformas.update.totals.subtotalBeforeDiscounts", "Subtotal")} + + + {formatMoney(totals.subtotalBeforeDiscounts)} + +
+ +
+ + } /> + + {t("proformas.update.totals.totalDiscountAmount", "Total descuentos")} + + + + + {t("proformas.update.totals.totalDiscountAmount", "Dtos")} + + {`-${formatMoney(totals.totalDiscountAmount)}`} +
+ +
+ + {t("proformas.update.totals.taxableBase", "Base imp.")} + + {formatMoney(totals.taxableBase)} +
+ +
+ + } /> + + {t("proformas.update.totals.taxTotal", "Total impuestos")} + + + + {t("proformas.update.totals.taxTotal", "Impuestos")} + + + {formatMoney(totals.taxRecTotal)} + +
+ + {showRetention ? ( +
+ + } /> + + {t("proformas.update.totals.retentionAmount", "Total retenciones")} + + + + {t("proformas.update.totals.retentionAmount", "Retenc.")} + + + {`-${formatMoney(totals.retentionAmount)}`} + +
+ ) : null} + + {/* Total destacado */} + + + + + + + Total{" "} + {formatMoney(totals.total)} + + + +
+
+
+ ); +}; diff --git a/modules/customer-invoices/src/web/proformas/update/ui/blocks/proforma-line-editor.tsx b/modules/customer-invoices/src/web/proformas/update/ui/blocks/proforma-line-editor.tsx index eead15fb..3a914024 100644 --- a/modules/customer-invoices/src/web/proformas/update/ui/blocks/proforma-line-editor.tsx +++ b/modules/customer-invoices/src/web/proformas/update/ui/blocks/proforma-line-editor.tsx @@ -1,6 +1,5 @@ import { AmountField, - CheckboxField, LineDescriptionField, PercentageField, QuantityField, @@ -66,12 +65,6 @@ export const ProformaLineEditor = ({ const { t } = useTranslation(); const columns: LineEditorColumn[] = [ - { - id: "isValued", - header: t("form_fields.items.description.is_valued", "¿Valorado?"), - headClassName: "w-[100px] text-right", - cell: ({ index }) => , - }, { id: "description", header: t("form_fields.items.description.label", "Descripción"), @@ -101,7 +94,7 @@ export const ProformaLineEditor = ({ { id: "unitAmount", header: t("form_fields.items.unit_amount.label", "Importe unitario"), - headClassName: "w-[120px] text-right", + headClassName: "text-right", cell: ({ index }) => ( ( MoneyHelper.formatCurrency(getItemAmounts(index).total, 2, currency), }, ]; diff --git a/modules/customer-invoices/src/web/proformas/update/ui/blocks/proforma-totals-summary.tsx b/modules/customer-invoices/src/web/proformas/update/ui/blocks/proforma-totals-summary.tsx index 7e1b219a..d18373f5 100644 --- a/modules/customer-invoices/src/web/proformas/update/ui/blocks/proforma-totals-summary.tsx +++ b/modules/customer-invoices/src/web/proformas/update/ui/blocks/proforma-totals-summary.tsx @@ -74,7 +74,7 @@ export const ProformaTotalsSummary = ({ value={`-${formatMoney(totals.lineDiscountTotal)}`} /> - {globalDiscountField ?
{globalDiscountField}
: null} + {globalDiscountField ? <>{globalDiscountField} : null}
@@ -204,8 +204,8 @@ const TotalsRow = ({ label, value, description, className, strong = false }: Tot
{value} diff --git a/modules/customer-invoices/src/web/proformas/update/ui/blocks/proforma-update-header.tsx b/modules/customer-invoices/src/web/proformas/update/ui/blocks/proforma-update-header.tsx new file mode 100644 index 00000000..973e1900 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/update/ui/blocks/proforma-update-header.tsx @@ -0,0 +1,97 @@ +import { + Button, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@repo/shadcn-ui/components"; +import { ArrowLeftIcon, KeyboardIcon, MoreHorizontalIcon, SaveIcon, XIcon } from "lucide-react"; + +export interface ProformaUpdateHeaderProps { + onSave?: () => void; + onCancel?: () => void; + + disabled?: boolean; + readOnly?: boolean; + hasChanges?: boolean; +} + +export const ProformaUpdateHeader = ({ + onSave, + onCancel, + disabled = false, + readOnly = false, + hasChanges = false, +}: ProformaUpdateHeaderProps) => { + return ( +
+
+
+ +
+

+ Editar proforma +

+ {hasChanges && } +
+
+ +
+ {/* Atajo de teclado info */} + + + + + } + /> + +
+
+ Ctrl+S Guardar +
+
+ Esc Cancelar +
+
+
+
+ + + + + + + + + + } + /> + + + Duplicar proforma + Exportar PDF + + Eliminar + + +
+
+
+ ); +}; diff --git a/modules/customer-invoices/src/web/proformas/update/ui/components/proforma-info-alert.tsx b/modules/customer-invoices/src/web/proformas/update/ui/components/proforma-info-alert.tsx index 28e2239b..d1cad852 100644 --- a/modules/customer-invoices/src/web/proformas/update/ui/components/proforma-info-alert.tsx +++ b/modules/customer-invoices/src/web/proformas/update/ui/components/proforma-info-alert.tsx @@ -1,11 +1,11 @@ // packages/rdx-ui/src/components/feedback/info-alert.tsx import { Alert, AlertDescription, AlertTitle } from "@repo/shadcn-ui/components"; import { cn } from "@repo/shadcn-ui/lib/utils"; -import { InfoIcon } from "lucide-react"; +import { AlertCircleIcon } from "lucide-react"; import type * as React from "react"; type ProformaInfoAlertProps = { - title: string; + title?: string; description?: string; children?: React.ReactNode; className?: string; @@ -26,27 +26,32 @@ export const ProformaInfoAlert = ({ return (
- +
- - {title} - + {title && ( + + {title} + + )} {(description || children) && ( {description ?? children} diff --git a/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-payment-editor.tsx b/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-payment-editor.tsx index fd19e61b..493d6a2c 100644 --- a/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-payment-editor.tsx +++ b/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-payment-editor.tsx @@ -38,7 +38,6 @@ export const ProformaUpdatePaymentEditor = ({ } title={t("form_groups.proformas.settings.title", "Configuración")} > - + - -
-
-
- + {/* Alerta informativa */} + + Importante: Esta proforma no tiene validez fiscal. + Puedes convertirla en factura cuando sea aceptada por el cliente. + - + {/* Área principal */} +
+
+ {/* Formulario de datos básicos */} + + + {/* Tabla de líneas */} +
+
+ + {/* Sidebar - desktop */} + +
+ +
+
+
+ +
-
+
-
+
+ + + + + + } + layout="vertical" + showRec={taxCtrl.hasRecPercentage} + showRetention={taxCtrl.hasRetentionPercentage} + totals={totalsCtrl.totals} + /> + -
- -
- - - -
+ {/* Footer fijo con totales */} +
+ + } + showRec={taxCtrl.hasRecPercentage} + showRetention={taxCtrl.hasRetentionPercentage} + totals={totalsCtrl.totals} + /> +
); }; diff --git a/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-header-editor.tsx b/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-header-editor.tsx index b61ba65d..724227d1 100644 --- a/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-header-editor.tsx +++ b/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-header-editor.tsx @@ -28,14 +28,13 @@ export const ProformaUpdateHeaderEditor = ({ return ( } title={t("form_groups.proformas.basic_info.title")} > Autom. } /> + - - - } - title={t("form_groups.items.title")} + icon={} + title={t("form_groups.items.title", "Líneas de detalle")} > { ); + updateCtrl.form.d; + return ( @@ -85,6 +88,10 @@ export const ProformaUpdatePage = () => { + {updateCtrl.isUpdateError && ( acc + item.recAmount, 0); + const taxRecTotal = roundMoney(taxTotal + recTotal); + const normalizedRetentionPercentage = retentionPercentage === null ? null : toCalculationNumber(retentionPercentage); @@ -67,6 +69,8 @@ export const calculateProformaTotalsFromLines = ({ recTotal: roundMoney(recTotal), + taxRecTotal: roundMoney(taxRecTotal), + retentionPercentage: normalizedRetentionPercentage, retentionAmount: roundMoney(retentionAmount),