feat(web): el stock por almacén muestra el nombre del almacén y la lista de artículos el nombre del grupo (columna nameOf en las tablas de filas)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
GeorgeandClaude Sonnet 5.5 committed 2026-10-19 18:59:56 -05:00
1 parent 5253ef4cb7
commit 8af3b11ac2
4 files changed
+29 -3

No files matched your search

@@ -70,6 +70,11 @@ export interface DetailColumn {
lookupFor?: (row: Record<string, unknown>) => VhLookup | undefined;
/** La celda no se edita en esta fila. */
readonlyFor?: (row: Record<string, unknown>) => boolean;
/**
* La columna muestra el nombre de lo que identifica el código de otra columna (`WarehouseCode` → nombre del almacén);
* el catálogo se consulta una sola vez. No es un campo que se guarde.
*/
nameOf?: { codeKey: string; resource: string; valueField: string; labelField: string };
/** Texto calculado de la fila (solo se muestra; no es un campo que se guarde). */
computed?: (row: Record<string, unknown>) => string;
/** Campos de la fila que cambian además del propio al elegir o escribir un valor de la lista (la cuenta elegida primero completa el «Cuenta / socio»). */
+20 -1
View File
@@ -1,4 +1,6 @@
import { HttpClient, HttpParams } from '@angular/common/http';
import { ChangeDetectionStrategy, Component, computed, effect, inject, input, model, signal } from '@angular/core';
import { API_URL } from '../../core/api';
import { formatNumber, parseDecimal } from '../../core/format';
import { SlLookupApi } from '../../core/sl/sl-lookup';
import type { ListRow } from '../list/list.types';
@@ -151,8 +153,24 @@ export class RowsTable {
readonly emptyText = input('Sin filas.');
private readonly lookupApi = inject(SlLookupApi);
private readonly http = inject(HttpClient);
/** Nombres de los catálogos de las columnas `nameOf`, por recurso. */
private readonly names = signal<Record<string, Map<string, string>>>({});
private readonly requested = new Set<string>();
constructor() {
effect(() => {
for (const c of this.columns()) {
const n = c.nameOf;
if (!n || this.requested.has(n.resource)) continue;
this.requested.add(n.resource);
const params = new HttpParams().set('$select', `${n.valueField},${n.labelField}`).set('$top', 500);
this.http.get<{ value?: Record<string, unknown>[] }>(`${API_URL}/sl/${n.resource}`, { params }).subscribe({
next: (res) => this.names.update((m) => ({ ...m, [n.resource]: new Map((res.value ?? []).map((x) => [String(x[n.valueField]), String(x[n.labelField] ?? '')])) })),
error: () => undefined, // sin nombres, la columna queda vacía
});
}
});
// las celdas de código con texto propio (la cuenta con su formato) piden los textos de las filas que se cargan
effect(() => {
const rows = this.rows();
@@ -210,11 +228,12 @@ export class RowsTable {
}
protected locked(c: DetailColumn, row?: Row): boolean {
return this.readonly() || !!c.readonly || !!c.computed || (!!row && !!c.readonlyFor?.(row));
return this.readonly() || !!c.readonly || !!c.computed || !!c.nameOf || (!!row && !!c.readonlyFor?.(row));
}
protected text(row: Row, c: DetailColumn): string {
if (c.computed) return c.computed(row);
if (c.nameOf) return this.names()[c.nameOf.resource]?.get(String(row[c.nameOf.codeKey] ?? '')) ?? '';
const v = row[c.key];
if (c.type === 'date') return dateOnly(v);
if (c.type === 'number') return v === null || v === undefined || v === '' ? '' : formatNumber(v, c.decimals);