feat(web): gastos adicionales de los documentos de ventas y compras como tabla de varias filas (antes solo el flete)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
GeorgeandClaude Sonnet 5.5 committed 2026-10-19 12:46:56 -05:00
1 parent 3c1d02a263
commit 6c2cb30d63
10 files changed
+223 -162

No files matched your search

@@ -7,7 +7,7 @@ import { SERIES_SUBTYPE } from './document-objects';
import { ANALYTIC_COLUMNS, DOCUMENT_LINE_COLUMNS, lineColumnsFor, lineFieldsOf, newDocumentLine, newServiceLine, recalcLine, serviceLineColumns, uomEntryOf } from './line-columns';
/** Valores calculados o del socio que una fila de solo lectura puede mostrar. */
export type ComputedSource = 'docNum' | 'partnerName' | 'partnerTaxId' | 'subtotal' | 'tax' | 'total';
export type ComputedSource = 'docNum' | 'partnerName' | 'partnerTaxId' | 'subtotal' | 'expenses' | 'tax' | 'total';
/**
* Un campo de la cabecera (o del pie) del formulario. La pantalla de cada documento declara su lista
@@ -42,7 +42,8 @@ export type HeaderField =
/** Serie de numeración y número del documento en una misma fila (`Series` y `DocNum`). */
| { type: 'series'; key: string; label: string }
/** Flete: código del gasto adicional y su importe (`DocumentAdditionalExpenses`). */
| { type: 'freight'; label: string }
/** Tabla de gastos adicionales del documento (flete, seguro…): una fila por gasto. */
| { type: 'expenses'; label: string }
| { type: 'computed'; label: string; source: ComputedSource };
/** Las listas de valores (ventanas modales) de un formulario. */
@@ -233,7 +234,7 @@ export function marketingLayout(opts: {
/** Unidad de medida que se toma del artículo: la de venta o la de compra. */
uom: 'sales' | 'purchase';
/** Fila del flete en el pie (por defecto sí). */
freight?: boolean;
expenses?: boolean;
/** Valores que se envían siempre al crear (ej. `DocumentSubType: 'bod_Bill'` en las boletas). */
fixed?: DocRecord;
}): DocumentLayout {
@@ -256,11 +257,11 @@ export function marketingLayout(opts: {
right: [FIELDS.docNumber, FIELDS.docDate, FIELDS.docDueDate, ...(opts.right ?? [])],
},
footer: {
left: [FIELDS.comments],
left: [...(opts.expenses === false ? [] : [{ type: 'expenses', label: 'Gastos adicionales' } as const]), FIELDS.comments],
right: [
{ type: 'computed', label: 'Total antes del descuento', source: 'subtotal' },
{ type: 'number', key: 'DiscountPercent', label: 'Descuento %', decimals: 2 },
...(opts.freight === false ? [] : [{ type: 'freight', label: 'Flete' } as const]),
{ type: 'computed', label: 'Gastos adicionales', source: 'expenses' },
{ type: 'computed', label: 'Impuesto', source: 'tax' },
{ type: 'computed', label: 'Total del documento', source: 'total' },
],
@@ -298,8 +299,8 @@ export const keysOfFields = (fields: readonly HeaderField[]): string[] =>
return [];
case 'folio':
return [f.prefixKey, f.numberKey];
case 'freight':
return ['FreightExpense', 'FreightAmount'];
case 'expenses':
return [];
default:
return [f.key];
}
@@ -322,6 +323,6 @@ export function isRequired(f: HeaderField): f is HeaderField & { key: string; re
export const hasSeries = (layout: DocumentLayout): boolean => allFields(layout).some((f) => f.type === 'series');
export const hasFreight = (layout: DocumentLayout): boolean => allFields(layout).some((f) => f.type === 'freight');
export const hasExpenses = (layout: DocumentLayout): boolean => allFields(layout).some((f) => f.type === 'expenses');
export const hasFolio = (layout: DocumentLayout): boolean => allFields(layout).some((f) => f.type === 'folio');