import type { GridColumn, GridLine, GridMultiAdd } from '../../shared/grid/grid.types'; const round2 = (n: number): number => Math.round((n + Number.EPSILON) * 100) / 100; const num = (v: unknown): number => { const n = Number(v ?? 0); return Number.isFinite(n) ? n : 0; }; /** Total de la línea antes de impuestos: cantidad × precio × (1 − descuento %). SAP lo recalcula al guardar. */ export function recalcLine(line: GridLine): GridLine { const total = num(line['Quantity']) * num(line['Price']) * (1 - num(line['DiscountPercent']) / 100); return { ...line, LineTotal: round2(total) }; } /** Línea nueva con los valores por defecto de un documento. */ export const newDocumentLine = (): GridLine => ({ ItemCode: '', ItemDescription: '', Quantity: 1, WarehouseCode: '', Price: 0, DiscountPercent: 0, TaxCode: '', LineTotal: 0, }); /** * Columnas estándar de una línea de documento de SAP B1 (artículo, descripción, cantidad, almacén, * precio, % descuento, código de impuesto y total). El impuesto es `TaxCode` (maestro `SalesTaxCodes`); * un cliente que use `VatGroup` cambia esa columna en su rama. */ export const DOCUMENT_LINE_COLUMNS: readonly GridColumn[] = [ { key: 'ItemCode', title: 'Nº de artículo', width: '140px', mono: true, required: true, lookup: { config: { title: 'Artículos', resource: 'Items', orderby: 'ItemCode', columns: [ { key: 'ItemCode', title: 'Código', width: '150px', mono: true }, { key: 'ItemName', title: 'Descripción' }, ], }, apply: (item, line) => ({ ...line, ItemCode: item['ItemCode'], ItemDescription: item['ItemName'] ?? line['ItemDescription'] }), }, }, { key: 'ItemDescription', title: 'Descripción' }, { key: 'Quantity', title: 'Cantidad', type: 'number', width: '85px', required: true }, { key: 'WarehouseCode', title: 'Almacén', width: '90px', mono: true, lookup: { config: { title: 'Almacenes', resource: 'Warehouses', orderby: 'WarehouseCode', columns: [ { key: 'WarehouseCode', title: 'Código', width: '110px', mono: true }, { key: 'WarehouseName', title: 'Nombre' }, ], }, apply: (wh, line) => ({ ...line, WarehouseCode: wh['WarehouseCode'] }), }, }, { key: 'Price', title: 'Precio', type: 'number', decimals: 2, width: '100px' }, { key: 'DiscountPercent', title: '% Dto.', type: 'number', decimals: 2, width: '70px' }, { key: 'TaxCode', title: 'Impuesto', width: '90px', mono: true, lookup: { config: { title: 'Códigos de impuesto', resource: 'SalesTaxCodes', orderby: 'Code', columns: [ { key: 'Code', title: 'Código', width: '110px', mono: true }, { key: 'Name', title: 'Nombre' }, { key: 'Rate', title: 'Tasa %', type: 'number', width: '80px' }, ], }, apply: (tax, line) => ({ ...line, TaxCode: tax['Code'] }), }, }, { key: 'LineTotal', title: 'Total', type: 'number', decimals: 2, width: '110px', readonly: true }, ]; /** Columnas de línea con el maestro de impuestos que corresponde (por defecto `SalesTaxCodes`). */ export function lineColumnsFor(taxResource: string): readonly GridColumn[] { if (taxResource === 'SalesTaxCodes') return DOCUMENT_LINE_COLUMNS; return DOCUMENT_LINE_COLUMNS.map((c) => c.key === 'TaxCode' && c.lookup ? { ...c, lookup: { ...c.lookup, config: { ...c.lookup.config, resource: taxResource } } } : c, ); } /** "Catálogo completo": agrega de una vez varios artículos como líneas nuevas. */ export const ITEMS_MULTI_ADD: GridMultiAdd = { label: 'Elegir artículos…', lookup: DOCUMENT_LINE_COLUMNS[0].lookup!.config, toLine: (item) => ({ ...newDocumentLine(), ItemCode: item['ItemCode'], ItemDescription: item['ItemName'] ?? '' }), };