feat(web): el stock por almacén totaliza en stock, comprometido y pedido en una fila bajo los títulos (columna total en las tablas de filas)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d05b497cb2
commit
a659276d9a
3 files changed
+30
-3
No files matched your search
@@ -152,9 +152,9 @@ export const ARTICULOS_SCREENS: readonly MasterScreen[] = [
|
|||||||
columns: [
|
columns: [
|
||||||
{ key: 'WarehouseCode', title: 'Almacén', type: 'text', width: '110px' },
|
{ 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: '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: 'InStock', title: 'En stock', type: 'number', width: '110px', total: true },
|
||||||
{ key: 'Committed', title: 'Comprometido', type: 'number', width: '110px' },
|
{ key: 'Committed', title: 'Comprometido', type: 'number', width: '110px', total: true },
|
||||||
{ key: 'Ordered', title: 'Pedido', type: 'number', width: '110px' },
|
{ key: 'Ordered', title: 'Pedido', type: 'number', width: '110px', total: true },
|
||||||
{ key: 'MinimalStock', title: 'Mínimo', type: 'number', width: '100px' },
|
{ key: 'MinimalStock', title: 'Mínimo', type: 'number', width: '100px' },
|
||||||
{ key: 'MaximalStock', title: 'Máximo', type: 'number', width: '100px' },
|
{ key: 'MaximalStock', title: 'Máximo', type: 'number', width: '100px' },
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -59,6 +59,8 @@ export interface DetailColumn {
|
|||||||
lookup?: VhLookup;
|
lookup?: VhLookup;
|
||||||
/** Decimales fijos de una columna numérica; sin él, hasta 3. */
|
/** Decimales fijos de una columna numérica; sin él, hasta 3. */
|
||||||
decimals?: number;
|
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
|
* 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.
|
* un proveedor). El botón «…» pregunta cuál abrir; un código escrito se busca en ellas en orden.
|
||||||
|
|||||||
@@ -34,6 +34,17 @@ const dateOnly = (v: unknown): string => (typeof v === 'string' ? v.slice(0, 10)
|
|||||||
<th class="del"></th>
|
<th class="del"></th>
|
||||||
}
|
}
|
||||||
</tr>
|
</tr>
|
||||||
|
@if (hasTotals() && rows().length) {
|
||||||
|
<tr>
|
||||||
|
<td class="rn"></td>
|
||||||
|
@for (c of columns(); track c.key; let first = $first) {
|
||||||
|
<td class="tot" [class.num]="c.total">{{ c.total ? totalOf(c) : first ? 'Total' : '' }}</td>
|
||||||
|
}
|
||||||
|
@if (canRemove()) {
|
||||||
|
<td class="del"></td>
|
||||||
|
}
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@for (row of rows(); track $index; let i = $index) {
|
@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); }
|
th.req::after { content: ' *'; color: var(--err); }
|
||||||
td { padding: 0 2px; height: 27px; border: 1px solid var(--line-soft); white-space: nowrap; }
|
td { padding: 0 2px; height: 27px; border: 1px solid var(--line-soft); white-space: nowrap; }
|
||||||
td.ro { background: var(--field-ro); }
|
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.rn, th.rn { width: 30px; text-align: center; color: var(--quiet); }
|
||||||
td.del, th.del { width: 30px; text-align: center; }
|
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); }
|
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();
|
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 {
|
protected locked(c: DetailColumn, row?: Row): boolean {
|
||||||
return this.readonly() || !!c.readonly || !!c.computed || !!c.nameOf || (!!row && !!c.readonlyFor?.(row));
|
return this.readonly() || !!c.readonly || !!c.computed || !!c.nameOf || (!!row && !!c.readonlyFor?.(row));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user