Files
basic-erp/apps/web/src/app/features/documents/line-columns.ts
T

154 lines
5.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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. El precio queda vacío (no 0): si el
* usuario no lo escribe no se envía y SAP aplica la lista de precios del socio de negocio.
*/
/**
* Unidad de medida de un artículo con grupo de unidades, para las líneas de un documento: la de
* compra (o de venta) por defecto o, si no tiene, la de inventario. Sin ella SAP rechaza el documento ("specify a
* UoM code"); y a un artículo de unidad manual (sin grupo) SAP no admite ponérsela.
*/
export function uomEntryOf(item: Record<string, unknown>, side: 'purchase' | 'sales' = 'purchase'): number | undefined {
if (!(Number(item['UoMGroupEntry']) > 0)) return undefined;
for (const key of [side === 'sales' ? 'DefaultSalesUoMEntry' : 'DefaultPurchasingUoMEntry', 'InventoryUoMEntry']) {
const entry = Number(item[key]);
if (entry > 0) return entry;
}
return undefined;
}
export const newDocumentLine = (): GridLine => ({
ItemCode: '',
ItemDescription: '',
Quantity: 1,
WarehouseCode: '',
Price: null,
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',
extraFields: ['UoMGroupEntry', 'DefaultPurchasingUoMEntry', 'DefaultSalesUoMEntry', 'InventoryUoMEntry'],
columns: [
{ key: 'ItemCode', title: 'Código', width: '150px', mono: true },
{ key: 'ItemName', title: 'Descripción' },
],
},
multiple: true,
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,
propagate: 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,
propagate: 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 },
];
const columnOf = (key: string): GridColumn => DOCUMENT_LINE_COLUMNS.find((c) => c.key === key)!;
/**
* Catálogo de columnas de línea: cada documento elige cuáles muestra (y en qué orden) en su pantalla.
* Sin elegir, lleva todas (`DOCUMENT_LINE_COLUMNS`).
*/
export const LINE_COLUMNS = {
item: columnOf('ItemCode'),
description: columnOf('ItemDescription'),
quantity: columnOf('Quantity'),
warehouse: columnOf('WarehouseCode'),
price: columnOf('Price'),
discount: columnOf('DiscountPercent'),
tax: columnOf('TaxCode'),
total: columnOf('LineTotal'),
} as const;
/** Campos de línea que se envían a SAP: los de las columnas editables (el total y lo calculado lo resuelve SAP). */
export const lineFieldsOf = (columns: readonly GridColumn[]): string[] =>
columns.filter((c) => !c.format && c.readonly !== true).map((c) => c.key);
/** Columnas de línea con el maestro de impuestos que corresponde (por defecto `SalesTaxCodes`). */
export function lineColumnsFor(taxResource: string, columns: readonly GridColumn[] = DOCUMENT_LINE_COLUMNS): readonly GridColumn[] {
if (taxResource === 'SalesTaxCodes') return columns;
return 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'] ?? '' }),
};