feat(web): fila del flete (gastos adicionales) y toolbar de ancho completo; la configuración de pantallas se carga en diferido

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
GeorgeandClaude Sonnet 5.5 committed 2026-10-14 16:09:06 -05:00
1 parent d3ab97cd2e
commit d6c127627f
14 files changed
+360 -27

No files matched your search

@@ -20,7 +20,12 @@ export function isDocumentReadOnly(doc: DocRecord | null | undefined): boolean {
// --- totales ------------------------------------------------------------------------------
/** Gasto adicional del documento (el flete): colección `DocumentAdditionalExpenses` de SAP. */
export const FREIGHT_COLLECTION = 'DocumentAdditionalExpenses';
export interface DocTotals {
/** Flete (gastos adicionales) antes de impuestos. */
freight: number;
/** Suma de los totales de línea (ya con el descuento de cada línea). */
subtotal: number;
/** Descuento del documento (% de cabecera aplicado al subtotal). */
@@ -44,6 +49,7 @@ export function computeTotals(
lines: readonly GridLine[],
rates: ReadonlyMap<string, number>,
discountPercent: number,
freight = 0,
): DocTotals {
const factor = 1 - discountPercent / 100;
let subtotal = 0;
@@ -59,10 +65,50 @@ export function computeTotals(
discount: round2(subtotal - net),
net: round2(net),
tax: round2(tax),
total: round2(net + tax),
freight: round2(freight),
total: round2(net + tax + freight),
};
}
// --- flete ---------------------------------------------------------------------------------
/** Flete del documento tal como se edita: el código del gasto y el importe (`FreightExpense`, `FreightAmount`). */
export function freightOf(doc: DocRecord | undefined): { expense: string; amount: number; editable: boolean } {
const rows = (doc?.[FREIGHT_COLLECTION] as DocRecord[] | undefined) ?? [];
return {
expense: rows[0]?.['ExpenseCode'] === undefined || rows[0]?.['ExpenseCode'] === null ? '' : String(rows[0]['ExpenseCode']),
amount: round2(rows.reduce((sum, r) => sum + num(r['LineTotal']), 0)),
// con varios gastos adicionales (cargados en SAP) el flete solo se muestra: no se puede reducir a una fila
editable: rows.length <= 1,
};
}
const FREIGHT_KEYS = ['FreightExpense', 'FreightAmount'] as const;
const withoutFreight = (r: DocRecord): DocRecord => Object.fromEntries(Object.entries(r).filter(([k]) => !(FREIGHT_KEYS as readonly string[]).includes(k)));
/**
* Convierte los campos del flete del formulario (`FreightExpense`, `FreightAmount`) en la colección
* `DocumentAdditionalExpenses` que espera SAP. `expense` es el código elegido o, si no eligió, el
* de flete por defecto. Al crear, sin importe no se envía nada; al modificar solo si cambió (con
* `replace`, porque SAP reemplaza la colección entera) y un importe 0 quita el flete.
*/
export function freightPayload(
body: DocRecord,
header: DocRecord,
expense: string,
mode: 'create' | 'update',
hadFreight = false,
): { body: DocRecord; replace: boolean } {
const touched = FREIGHT_KEYS.some((k) => k in body);
if (mode === 'update' && !touched) return { body, replace: false };
const amount = round2(num(header['FreightAmount']));
const code = isEmpty(header['FreightExpense']) ? expense : String(header['FreightExpense']);
const rest = withoutFreight(body);
if (amount > 0 && code !== '') return { body: { ...rest, [FREIGHT_COLLECTION]: [{ ExpenseCode: Number(code), LineTotal: amount }] }, replace: true };
if (mode === 'update' && hadFreight) return { body: { ...rest, [FREIGHT_COLLECTION]: [] }, replace: true };
return { body: rest, replace: false };
}
// --- guardado -----------------------------------------------------------------------------
/** Campos de línea que se envían a SAP; el total de línea y lo calculado lo resuelve SAP. */