diff --git a/apps/web/src/app/features/masters/articulos/articulos.screens.ts b/apps/web/src/app/features/masters/articulos/articulos.screens.ts index 0c3795e..d295792 100644 --- a/apps/web/src/app/features/masters/articulos/articulos.screens.ts +++ b/apps/web/src/app/features/masters/articulos/articulos.screens.ts @@ -152,9 +152,9 @@ export const ARTICULOS_SCREENS: readonly MasterScreen[] = [ columns: [ { key: 'WarehouseCode', title: 'Almacén', type: 'text', width: '110px' }, { key: 'WarehouseName', title: 'Nombre', type: 'text', width: '260px', nameOf: { codeKey: 'WarehouseCode', resource: 'Warehouses', valueField: 'WarehouseCode', labelField: 'WarehouseName' } }, - { key: 'InStock', title: 'En stock', type: 'number', width: '110px' }, - { key: 'Committed', title: 'Comprometido', type: 'number', width: '110px' }, - { key: 'Ordered', title: 'Pedido', type: 'number', width: '110px' }, + { key: 'InStock', title: 'En stock', type: 'number', width: '110px', total: true }, + { key: 'Committed', title: 'Comprometido', type: 'number', width: '110px', total: true }, + { key: 'Ordered', title: 'Pedido', type: 'number', width: '110px', total: true }, { key: 'MinimalStock', title: 'Mínimo', type: 'number', width: '100px' }, { key: 'MaximalStock', title: 'Máximo', type: 'number', width: '100px' }, ], diff --git a/apps/web/src/app/shared/grid/grid.types.ts b/apps/web/src/app/shared/grid/grid.types.ts index b144f79..d9c71e3 100644 --- a/apps/web/src/app/shared/grid/grid.types.ts +++ b/apps/web/src/app/shared/grid/grid.types.ts @@ -59,6 +59,8 @@ export interface DetailColumn { lookup?: VhLookup; /** Decimales fijos de una columna numérica; sin él, hasta 3. */ decimals?: number; + /** Se suma la columna en una fila «Total» al pie de la tabla (stock por almacén). */ + total?: boolean; /** * Una celda que puede tomar su valor de varias listas (la «Cuenta / socio» de un asiento: una cuenta, un cliente o * un proveedor). El botón «…» pregunta cuál abrir; un código escrito se busca en ellas en orden. diff --git a/apps/web/src/app/shared/grid/rows-table.ts b/apps/web/src/app/shared/grid/rows-table.ts index a8263b2..8ce887f 100644 --- a/apps/web/src/app/shared/grid/rows-table.ts +++ b/apps/web/src/app/shared/grid/rows-table.ts @@ -34,6 +34,17 @@ const dateOnly = (v: unknown): string => (typeof v === 'string' ? v.slice(0, 10) } + @if (hasTotals() && rows().length) { + + + @for (c of columns(); track c.key; let first = $first) { + {{ c.total ? totalOf(c) : first ? 'Total' : '' }} + } + @if (canRemove()) { + + } + + } @for (row of rows(); track $index; let i = $index) { @@ -132,6 +143,8 @@ const dateOnly = (v: unknown): string => (typeof v === 'string' ? v.slice(0, 10) th.req::after { content: ' *'; color: var(--err); } td { padding: 0 2px; height: 27px; border: 1px solid var(--line-soft); white-space: nowrap; } td.ro { background: var(--field-ro); } + td.tot { padding: 0 8px; font-weight: 600; background: var(--grid-head); } + td.tot.num { text-align: right; } td.rn, th.rn { width: 30px; text-align: center; color: var(--quiet); } td.del, th.del { width: 30px; text-align: center; } input, select { width: 100%; height: 22px; padding: 1px 4px; background: transparent; border: 1px solid transparent; border-radius: var(--radius); } @@ -227,6 +240,18 @@ export class RowsTable { return !this.readonly() && this.removable(); } + protected readonly hasTotals = computed(() => this.columns().some((c) => c.total)); + + /** La suma de una columna numérica en todas las filas. */ + protected totalOf(c: DetailColumn): string { + let sum = 0; + for (const r of this.rows()) { + const v = Number(r[c.key]); + if (Number.isFinite(v)) sum += v; + } + return formatNumber(sum, c.decimals); + } + protected locked(c: DetailColumn, row?: Row): boolean { return this.readonly() || !!c.readonly || !!c.computed || !!c.nameOf || (!!row && !!c.readonlyFor?.(row)); }