feat(web): celdas de cuenta con código con formato (asientos y documentos): se ven con separadores y se pueden escribir por FormatCode

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
GeorgeandClaude Sonnet 5.5 committed 2026-10-19 10:05:55 -05:00
1 parent a10de1ff29
commit 2733339c7f
9 files changed
+201 -5

No files matched your search

+20 -2
View File
@@ -5,6 +5,7 @@ import {
Injector,
afterNextRender,
computed,
effect,
inject,
input,
model,
@@ -57,6 +58,17 @@ export class LinesGrid {
private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);
private readonly injector = inject(Injector);
constructor() {
// las celdas de código con texto propio (la cuenta con su formato) piden los textos de las líneas que se cargan
effect(() => {
const lines = this.lines();
for (const c of this.columns()) {
const codes = c.lookup?.config.codes;
if (codes) codes.ensure(lines.map((l) => String(l[c.key] ?? '')).filter((v) => v !== ''));
}
});
}
protected readonly lookupTarget = signal<LookupTarget | null>(null);
protected readonly detailTarget = signal<{ row: number; detail: GridDetail } | null>(null);
protected readonly detailHintFn = computed(() => {
@@ -104,6 +116,8 @@ export class LinesGrid {
if (column.format) return column.format(line);
const value = line[column.key];
if (column.type === 'number') return formatNumber(value, column.decimals);
const codes = column.lookup?.config.codes;
if (codes && typeof value === 'string' && value !== '') return codes.display(value);
return value === null || value === undefined ? '' : String(value);
}
@@ -142,7 +156,7 @@ export class LinesGrid {
const value = parsed === null || d === undefined ? parsed : Math.round((parsed + Number.EPSILON) * 10 ** d) / 10 ** d;
this.update(row, column.key, value);
input.value = formatNumber(value, column.decimals);
} else if (text !== String(line[column.key] ?? '')) {
} else if (text !== String(line[column.key] ?? '') && text !== this.cellText(line, column)) {
if (column.lookup && text.trim() !== '') void this.resolveTyped(row, column, input, text.trim());
else this.update(row, column.key, text);
}
@@ -164,7 +178,10 @@ export class LinesGrid {
const found = await this.injector.get(SlLookupApi).find(lookup.config, code);
const line = this.lines()[row];
if (!line) return;
if (found) this.replace(row, lookup.apply(found, line), column.key);
if (found) {
lookup.config.codes?.remember(found);
this.replace(row, lookup.apply(found, line), column.key);
}
else {
input.value = this.cellText(line, column);
this.notice.emit(`No existe «${code}» en ${lookup.config.title.toLowerCase()}.`);
@@ -287,6 +304,7 @@ export class LinesGrid {
const line = target ? this.lines()[target.row] : undefined;
if (!target || !picked || !line || !target.column.lookup) return;
const lookup = target.column.lookup;
lookup.config.codes?.remember(picked);
this.replace(target.row, lookup.apply(picked, line), target.column.key);
const rest = rows.slice(1).map((r) => {
const added = lookup.apply(r, this.newLine()());
+37 -1
View File
@@ -1,5 +1,6 @@
import { ChangeDetectionStrategy, Component, computed, input, model, signal } from '@angular/core';
import { ChangeDetectionStrategy, Component, computed, effect, inject, input, model, signal } from '@angular/core';
import { formatNumber, parseDecimal } from '../../core/format';
import { SlLookupApi } from '../../core/sl/sl-lookup';
import type { ListRow } from '../list/list.types';
import { ValueHelp } from '../value-help/value-help';
import type { DetailColumn } from './grid.types';
@@ -137,6 +138,19 @@ export class RowsTable {
readonly removable = input(true);
readonly emptyText = input('Sin filas.');
private readonly lookupApi = inject(SlLookupApi);
constructor() {
// 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();
for (const c of this.columns()) {
const codes = c.lookup?.codes;
if (codes) codes.ensure(rows.map((r) => String(r[c.key] ?? '')).filter((v) => v !== ''));
}
});
}
/** La ventana de selección abierta: qué fila y de qué columna. */
protected readonly target = signal<{ row: number; col: DetailColumn; lookup: NonNullable<DetailColumn['lookup']> } | null>(null);
@@ -149,6 +163,7 @@ export class RowsTable {
const picked = rows[0];
if (!t || !picked) return;
const key = t.lookup.valueKey ?? t.lookup.columns[0]?.key ?? '';
t.lookup.codes?.remember(picked);
this.rows.update((rs) => rs.map((r, i) => (i === t.row ? { ...r, [t.col.key]: picked[key] ?? '' } : r)));
}
@@ -164,6 +179,7 @@ export class RowsTable {
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);
if (c.type === 'lookup' && c.lookup?.codes && typeof v === 'string' && v !== '') return c.lookup.codes.display(v);
return v === null || v === undefined ? '' : String(v);
}
@@ -188,5 +204,25 @@ export class RowsTable {
input.value = parsed === null ? '' : formatNumber(parsed, c.decimals);
}
this.rows.update((rs) => rs.map((r, i) => (i === index ? { ...r, [c.key]: value } : r)));
if (c.type === 'lookup' && c.lookup?.codes && raw.trim() !== '') void this.resolveTyped(index, c, raw.trim());
}
/**
* Un código escrito a mano en una celda con texto propio (la cuenta) se busca por cualquiera de sus códigos y se
* guarda el `Code` de SAP. Si no se encuentra se deja lo escrito: la celda también admite códigos de otro tipo
* (el `ShortName` de un asiento puede ser un socio) y SAP valida al guardar.
*/
private async resolveTyped(index: number, c: DetailColumn, code: string): Promise<void> {
const lookup = c.lookup!;
try {
const found = await this.lookupApi.find(lookup, code);
if (!found) return;
lookup.codes?.remember(found);
const key = lookup.valueKey ?? lookup.columns[0]?.key ?? '';
const value = found[key] ?? code;
this.rows.update((rs) => rs.map((r, i) => (i === index && r[c.key] === code ? { ...r, [c.key]: value } : r)));
} catch {
/* sin conexión: queda lo escrito */
}
}
}
@@ -17,4 +17,17 @@ export interface VhLookup {
extraFields?: readonly string[];
/** Tipo de la clave de la lista: `number` para códigos numéricos (grupos, listas de precios), que no llevan comillas. */
valueType?: 'string' | 'number';
/** Otros campos por los que se reconoce un código escrito a mano (`FormatCode` de una cuenta: el usuario no conoce el `_SYS…` interno). */
alsoMatch?: readonly string[];
/** Cómo se muestra el código guardado en una celda (la cuenta con su formato) y cómo se aprenden los de las filas elegidas. */
codes?: CodeDisplay;
}
/** Texto que ve el usuario en vez del código guardado; los códigos se piden al servidor la primera vez que se necesitan. */
export interface CodeDisplay {
display(code: string): string;
/** Pide los textos de estos códigos si aún no se tienen. */
ensure(codes: readonly string[]): void;
/** Aprende el texto de una fila elegida o encontrada. */
remember(row: VhRow): void;
}