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:
1 parent
a10de1ff29
commit
2733339c7f
9 files changed
+201
-5
No files matched your search
@@ -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()());
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in new issue
Block a user