feat(web): la lista de clientes y proveedores muestra el Grupo (código y nombre, una sola consulta al catálogo)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
GeorgeandClaude Sonnet 5.5 committed 2026-10-19 18:50:16 -05:00
1 parent 8ed44aa55b
commit f985667b90
3 files changed
+35 -3

No files matched your search

@@ -1,7 +1,9 @@
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; import { HttpClient, HttpParams } from '@angular/common/http';
import { ChangeDetectionStrategy, Component, inject, signal } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { API_URL } from '../../core/api';
import { ListView } from '../../shared/list/list-view'; import { ListView } from '../../shared/list/list-view';
import type { ListRow } from '../../shared/list/list.types'; import type { ListColumn, ListRow } from '../../shared/list/list.types';
import type { MasterScreen } from './master-types'; import type { MasterScreen } from './master-types';
/** Lista de un maestro (clientes, proveedores, artículos); abre uno o crea uno nuevo. */ /** Lista de un maestro (clientes, proveedores, artículos); abre uno o crea uno nuevo. */
@@ -13,7 +15,7 @@ import type { MasterScreen } from './master-types';
<sbo-list-view <sbo-list-view
[title]="title" [title]="title"
[resource]="screen.resource" [resource]="screen.resource"
[columns]="screen.columns" [columns]="columns"
[extraFields]="screen.extraFields ?? []" [extraFields]="screen.extraFields ?? []"
[filters]="screen.filters" [filters]="screen.filters"
[orderby]="screen.orderby" [orderby]="screen.orderby"
@@ -32,6 +34,32 @@ export class MasterListPage {
protected readonly screen = this.route.snapshot.data['screen'] as MasterScreen; protected readonly screen = this.route.snapshot.data['screen'] as MasterScreen;
protected readonly title = this.route.snapshot.data['title'] as string; protected readonly title = this.route.snapshot.data['title'] as string;
private readonly http = inject(HttpClient);
private readonly names = signal<Record<string, Map<string, string>>>({});
/** Las columnas de la pantalla; las de un catálogo muestran `código · nombre` apenas llega el catálogo (una sola consulta). */
protected readonly columns: readonly ListColumn[] = this.screen.columns.map((c) => {
if (!this.screen.listNames?.some((n) => n.key === c.key)) return c;
return {
...c,
format: (r: ListRow) => {
const code = r[c.key];
if (code === null || code === undefined || code === '') return '';
const name = this.names()[c.key]?.get(String(code));
return name ? `${code} · ${name}` : String(code);
},
};
});
constructor() {
for (const n of this.screen.listNames ?? []) {
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.key]: new Map((res.value ?? []).map((x) => [String(x[n.valueField]), String(x[n.labelField] ?? '')])) })),
error: () => undefined, // sin nombres se ve el código
});
}
}
protected open(row: ListRow): void { protected open(row: ListRow): void {
void this.router.navigate([String(row[this.screen.keyField])], { relativeTo: this.route }); void this.router.navigate([String(row[this.screen.keyField])], { relativeTo: this.route });
@@ -71,6 +71,8 @@ export interface MasterScreen {
createLabel: string; createLabel: string;
columns: readonly ListColumn[]; columns: readonly ListColumn[];
extraFields?: readonly string[]; extraFields?: readonly string[];
/** Columnas de la lista cuyo valor es un código de otro catálogo: se muestra `código · nombre` (`GroupCode` → `BusinessPartnerGroups`). */
listNames?: readonly { key: string; resource: string; valueField: string; labelField: string }[];
filters: readonly ListFilter[]; filters: readonly ListFilter[];
orderby: string; orderby: string;
/** Filtro fijo de la lista y de la navegación (ej. solo los clientes). */ /** Filtro fijo de la lista y de la navegación (ej. solo los clientes). */
@@ -40,10 +40,12 @@ const partner = (opts: { path: string; type: 'cCustomer' | 'cSupplier'; singular
orderby: 'CardCode', orderby: 'CardCode',
baseFilter: `CardType eq '${opts.type}'`, baseFilter: `CardType eq '${opts.type}'`,
extraFields: ['Frozen'], extraFields: ['Frozen'],
listNames: [{ key: 'GroupCode', resource: 'BusinessPartnerGroups', valueField: 'Code', labelField: 'Name' }],
columns: [ columns: [
{ key: 'CardCode', title: 'Código', width: '130px', mono: true, searchable: true }, { key: 'CardCode', title: 'Código', width: '130px', mono: true, searchable: true },
{ key: 'CardName', title: 'Nombre', searchable: true }, { key: 'CardName', title: 'Nombre', searchable: true },
{ key: 'FederalTaxID', title: 'RUT', width: '120px', mono: true, searchable: true }, { key: 'FederalTaxID', title: 'RUT', width: '120px', mono: true, searchable: true },
{ key: 'GroupCode', title: 'Grupo', type: 'number', align: 'left', width: '170px' },
{ key: 'Phone1', title: 'Teléfono', width: '130px', searchable: true }, { key: 'Phone1', title: 'Teléfono', width: '130px', searchable: true },
{ key: 'EmailAddress', title: 'Correo electrónico', searchable: true }, { key: 'EmailAddress', title: 'Correo electrónico', searchable: true },
statusColumn, statusColumn,