feat(web): columnas de las listas de valores (cliente, artículo, almacén, impuesto) configurables por documento
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b3b47c34a3
commit
0d5a3074c8
6 files changed
+141
-12
No files matched your search
@@ -58,7 +58,9 @@ Hecho (commits en `main`): monorepo · API (config, capa SAP con sesión compart
|
||||
Pendiente, en orden:
|
||||
1. **Paso 9 — formulario de documentos:** hecho para ventas y compras (`document-form.page`, `document-model.ts`: cargar, crear, actualizar con `If-Match`/412, navegación, totales con `SalesTaxCodes` (también en compras), solo lectura), probado con SAP simulado y contra `SBODemoCL` (leer, crear, actualizar, líneas, 412). Un código escrito a mano (socio, artículo, almacén, impuesto) se resuelve al salir del campo con `SlLookupApi` (mismo recurso y filtro base que la lista "…"); si no existe se avisa. "Copiar de/a" hecho (`document-copy.ts`: flujos estándar y `BaseType/BaseEntry/BaseLine`; probado: orden de compra 2883 → recepción 299, orden de venta 315 → entrega 580, ambas órdenes quedaron cerradas). Formularios sin socio ni totales **hechos** (`document-layouts.ts`, configuración declarativa: cabecera de 2 columnas, columnas de línea, colección `DocumentLines` o `StockTransferLines`): entradas y salidas de inventario, transferencias de stock, solicitudes de traslado y solicitudes de compra. Entradas, salidas y transferencias contabilizadas son de solo lectura (SAP no las modifica); solicitudes de traslado y de compra se crean y modifican. Probado contra `SBODemoCL`: solicitud de compra 2 (la 1 es una prueba por API), solicitud de traslado 472 (cantidad modificada) y entrada de inventario 155 (1 unidad de `3MG30080714 M 10.5` en el almacén 8803). Limitaciones conocidas: los artículos por **lote o serie** no se pueden contabilizar (SAP pide `BatchNumbers`/`SerialNumbers`); las líneas de compra/inventario llevan `UoMEntry` solo si el artículo tiene grupo de unidades (`uomEntryOf`; sin él SAP responde "specify a UoM code"); los formularios de ventas y compras (`marketing`) aún no lo envían, y los de compra fallan con artículos sin unidad de compra por defecto. **Cómo se define un formulario:** el formulario base (`document-form.page`) no sabe de qué documento se trata; recibe de la pantalla su `layout` (`DocumentLayout` en `document-layouts.ts`): `header` y `footer` (cada uno con `left`/`right`, máximo 2 columnas) son arreglos de campos tomados del catálogo `FIELDS` (texto, fecha, número, almacén, socio, folio, calculado), `columns` son las columnas de línea y `lines` la colección que se guarda. Cada pantalla (en la carpeta de su módulo) declara los suyos (la orden de venta no lleva folio; las facturas, notas de crédito, entregas y recepciones sí, con `TAX_DOCUMENT`); agregar o quitar un campo es editar ese arreglo, sin tocar el HTML. En ventas y compras la pantalla puede agregar campos (`headerLeft`/`headerRight`), reemplazar la cabecera (`header`) o elegir sus columnas de línea del catálogo `LINE_COLUMNS` (`lineColumns: [LINE_COLUMNS.item, LINE_COLUMNS.quantity, …]`; lo que se envía por línea sale de ellas); sin elegir, el documento lleva la cabecera y todas las columnas base.
|
||||
|
||||
**Campos por cliente (estándar adicionales o `U_*`):** no se editan las pantallas base. Cada módulo tiene su archivo de extensiones, vacío en `main`: `ventas/ventas.extensions.ts`, `compras/compras.extensions.ts` e `inventario/inventario.extensions.ts` (reunidos en `client-extensions.ts`). La rama `cliente/<nombre>` declara ahí, por ruta de pantalla, qué suma: `headerLeft`/`headerRight`/`footerLeft`/`footerRight` (campos; tipos `text`, `date`, `number`, `textarea`, `select` con `options`, `warehouse`…), `lineColumns` (se agregan antes del total y se envían a SAP) y `listColumns`. Solo suma, no quita; una ruta inexistente o ajena al módulo lanza un error al arrancar. El test «el proyecto base no usa campos de usuario» revisa `BASE_SCREENS`, no las extensiones. Verificado: el proxy `/api/sl` deja pasar nombres `U_*` en `$select`/`$orderby` y en el cuerpo (el error de un campo inexistente lo da SAP). Límite: una columna de línea de usuario con lista de valores válidos aún no tiene editor desplegable (solo texto/número). Los formularios con socio muestran `NumAtCard` y el RUT (`FederalTaxID`, solo lectura: lo completa SAP desde el socio); facturas, notas de crédito, entregas y recepciones (`FOLIO_PATHS` en `document-screens.ts`) añaden el folio tributario, que en el Service Layer es `FolioPrefixString` + `FolioNumber` (se edita como texto y se envía como número); las listas de facturas también los muestran. Falta: botón PDF, fila del flete y toolbar de ancho completo.
|
||||
**Campos por cliente (estándar adicionales o `U_*`):** no se editan las pantallas base. Cada módulo tiene su archivo de extensiones, vacío en `main`: `ventas/ventas.extensions.ts`, `compras/compras.extensions.ts` e `inventario/inventario.extensions.ts` (reunidos en `client-extensions.ts`). La rama `cliente/<nombre>` declara ahí, por ruta de pantalla, qué suma: `headerLeft`/`headerRight`/`footerLeft`/`footerRight` (campos; tipos `text`, `date`, `number`, `textarea`, `select` con `options`, `warehouse`…), `lineColumns` (se agregan antes del total y se envían a SAP) y `listColumns`. Solo suma, no quita; una ruta inexistente o ajena al módulo lanza un error al arrancar. El test «el proyecto base no usa campos de usuario» revisa `BASE_SCREENS`, no las extensiones. Verificado: el proxy `/api/sl` deja pasar nombres `U_*` en `$select`/`$orderby` y en el cuerpo (el error de un campo inexistente lo da SAP). Límite: una columna de línea de usuario con lista de valores válidos aún no tiene editor desplegable (solo texto/número).
|
||||
|
||||
**Columnas de las ventanas de selección (cliente, artículo, almacén, impuesto):** son configurables por documento. En una pantalla del base (ventas/compras): `lookups: { item: [...], partner: [...], warehouse: [...], tax: [...] }` en el último parámetro de `marketing(...)` *sustituye* las columnas de esa lista (la primera columna es el código que se escribe en el campo). En la rama del cliente: `lookupColumns` en su archivo de extensiones *suma* columnas al final (por ejemplo `U_Marca` en artículos). El recurso, el filtro y el orden de cada lista no cambian; para los documentos de inventario o solicitudes se usa `withLookupColumns(layout, cols, 'replace' | 'add')`. Las columnas de almacén aplican también al almacén de la cabecera y al de origen de una transferencia. Los formularios con socio muestran `NumAtCard` y el RUT (`FederalTaxID`, solo lectura: lo completa SAP desde el socio); facturas, notas de crédito, entregas y recepciones (`FOLIO_PATHS` en `document-screens.ts`) añaden el folio tributario, que en el Service Layer es `FolioPrefixString` + `FolioNumber` (se edita como texto y se envía como número); las listas de facturas también los muestran. Falta: botón PDF, fila del flete y toolbar de ancho completo.
|
||||
2. Pantallas de maestros (`Items`, `BusinessPartners`: Clientes/Proveedores, Artículos), hoy "no disponible".
|
||||
3. ~~Pantalla web de permisos~~ — hecha (`/admin/permisos`).
|
||||
4. Auditoría (`audit_log` en SQLite; hoy solo líneas `AUDIT` en el log) y `nestjs-cls` para el usuario en los logs.
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
updatePayload,
|
||||
validateLayout,
|
||||
} from './document-model';
|
||||
import { type ComputedSource, WAREHOUSE_LOOKUP, hasFolio, headerKeysOf, requiredHeaderOf } from './document-layouts';
|
||||
import { type ComputedSource, PARTNER_COLUMNS, WAREHOUSE_LOOKUP, hasFolio, headerKeysOf, requiredHeaderOf } from './document-layouts';
|
||||
import { type CopyOrigin, copyFromDocument, copySources, copyTargets } from './document-copy';
|
||||
import { DOCUMENT_SCREENS, type DocumentScreen, docNum, documentStatus, menuLabel } from './document-screens';
|
||||
import { ITEMS_MULTI_ADD } from './line-columns';
|
||||
@@ -253,22 +253,24 @@ export class DocumentFormPage {
|
||||
// --- configuración de la vista --------------------------------------------------------------
|
||||
|
||||
protected readonly columns = this.layout.columns;
|
||||
private readonly itemColumn = this.layout.columns.find((c) => c.key === 'ItemCode') ?? this.layout.columns[0];
|
||||
protected readonly newLine = () => this.layout.newLine(this.header());
|
||||
protected readonly recalc = this.layout.recalc;
|
||||
protected readonly multi: GridMultiAdd = {
|
||||
...ITEMS_MULTI_ADD,
|
||||
toLine: (item) => this.layout.columns[0].lookup!.apply(item, this.layout.newLine(this.header())),
|
||||
lookup: this.itemColumn.lookup!.config,
|
||||
toLine: (item) => this.itemColumn.lookup!.apply(item, this.layout.newLine(this.header())),
|
||||
};
|
||||
protected readonly warehouseLookup: VhLookup = {
|
||||
...WAREHOUSE_LOOKUP,
|
||||
columns: this.layout.lookups?.warehouse ?? WAREHOUSE_LOOKUP.columns,
|
||||
};
|
||||
protected readonly warehouseLookup = WAREHOUSE_LOOKUP;
|
||||
protected readonly partnerLookup: VhLookup = {
|
||||
title: this.partner?.type === 'cSupplier' ? 'Proveedores' : 'Clientes',
|
||||
resource: 'BusinessPartners',
|
||||
baseFilter: `CardType eq '${this.partner?.type ?? 'cCustomer'}'`,
|
||||
orderby: 'CardCode',
|
||||
columns: [
|
||||
{ key: 'CardCode', title: 'Código', width: '130px', mono: true },
|
||||
{ key: 'CardName', title: 'Nombre' },
|
||||
],
|
||||
columns: this.layout.lookups?.partner ?? PARTNER_COLUMNS,
|
||||
extraFields: ['FederalTaxID'],
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { GridColumn, GridLine } from '../../shared/grid/grid.types';
|
||||
import type { ListColumn } from '../../shared/list/list.types';
|
||||
import type { VhLookup } from '../../shared/value-help/value-help.types';
|
||||
import type { DocRecord, LinesSpec } from './document-model';
|
||||
import { DOCUMENT_LINE_COLUMNS, lineColumnsFor, lineFieldsOf, newDocumentLine, recalcLine, uomEntryOf } from './line-columns';
|
||||
@@ -25,6 +26,12 @@ export type HeaderField =
|
||||
| { type: 'folio'; label: string; prefixKey: string; numberKey: string }
|
||||
| { type: 'computed'; label: string; source: ComputedSource };
|
||||
|
||||
/** Las listas de valores (ventanas modales) de un formulario. */
|
||||
export type LookupName = 'partner' | 'item' | 'warehouse' | 'tax';
|
||||
|
||||
/** Columnas que muestra cada lista de valores; la primera es el código que se escribe en el campo. */
|
||||
export type LookupColumns = Partial<Record<LookupName, readonly ListColumn[]>>;
|
||||
|
||||
export interface FieldColumns {
|
||||
/** Dos columnas como máximo (regla del proyecto base). */
|
||||
left: readonly HeaderField[];
|
||||
@@ -47,6 +54,8 @@ export interface DocumentLayout {
|
||||
newLine: (header: DocRecord) => GridLine;
|
||||
/** Recalcula una línea tras un cambio (total = cantidad × precio…). */
|
||||
recalc?: (line: GridLine, changedKey: string) => GridLine;
|
||||
/** Columnas de las listas de valores de este documento; las que falten usan las del base. */
|
||||
lookups?: LookupColumns;
|
||||
/** Una vez contabilizado ya no se modifica (SAP no permite cambiar entradas, salidas ni transferencias). */
|
||||
immutable?: boolean;
|
||||
/** Ajustes a la cabecera antes de crear. */
|
||||
@@ -91,6 +100,45 @@ export const itemColumn: GridColumn = {
|
||||
/** Lista de almacenes de la cabecera (la misma de las líneas). */
|
||||
export const WAREHOUSE_LOOKUP: VhLookup = warehouseColumn.lookup!.config;
|
||||
|
||||
/** Columnas del base de la lista de clientes y proveedores. */
|
||||
export const PARTNER_COLUMNS: readonly ListColumn[] = [
|
||||
{ key: 'CardCode', title: 'Código', width: '130px', mono: true },
|
||||
{ key: 'CardName', title: 'Nombre' },
|
||||
];
|
||||
|
||||
/** Columna de línea que abre cada lista de valores. */
|
||||
const LOOKUP_OF_COLUMN: Readonly<Record<string, LookupName>> = {
|
||||
ItemCode: 'item',
|
||||
WarehouseCode: 'warehouse',
|
||||
FromWarehouseCode: 'warehouse',
|
||||
TaxCode: 'tax',
|
||||
};
|
||||
|
||||
const defaultLookupColumns = (layout: DocumentLayout, name: LookupName): readonly ListColumn[] => {
|
||||
if (layout.lookups?.[name]) return layout.lookups[name]!;
|
||||
if (name === 'partner') return PARTNER_COLUMNS;
|
||||
return layout.columns.find((c) => LOOKUP_OF_COLUMN[c.key] === name)?.lookup?.config.columns ?? [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Cambia las columnas de las listas de valores de un documento. `replace` las sustituye por las
|
||||
* indicadas (el base lo usa); `add` las suma a las que ya tiene (lo usan las extensiones de cliente).
|
||||
* El recurso, el filtro y el orden de cada lista no cambian.
|
||||
*/
|
||||
export function withLookupColumns(layout: DocumentLayout, changes: LookupColumns, mode: 'replace' | 'add'): DocumentLayout {
|
||||
const next: Partial<Record<LookupName, readonly ListColumn[]>> = { ...layout.lookups };
|
||||
for (const name of Object.keys(changes) as LookupName[]) {
|
||||
const given = changes[name] ?? [];
|
||||
next[name] = mode === 'add' ? [...defaultLookupColumns(layout, name), ...given] : given;
|
||||
}
|
||||
const columns = layout.columns.map((c) => {
|
||||
const name = LOOKUP_OF_COLUMN[c.key];
|
||||
const cols = name ? next[name] : undefined;
|
||||
return cols && c.lookup ? { ...c, lookup: { ...c.lookup, config: { ...c.lookup.config, columns: cols } } } : c;
|
||||
});
|
||||
return { ...layout, columns, lookups: next };
|
||||
}
|
||||
|
||||
export const blankLine = (): GridLine => ({ ItemCode: '', ItemDescription: '', Quantity: 1, WarehouseCode: '' });
|
||||
|
||||
// --- documentos con socio de negocio (ventas y compras) -------------------------------------
|
||||
@@ -110,9 +158,11 @@ export function marketingLayout(opts: {
|
||||
right?: readonly HeaderField[];
|
||||
header?: FieldColumns;
|
||||
lineColumns?: readonly GridColumn[];
|
||||
/** Columnas de las listas de valores (cliente, artículo, almacén, impuesto) de este documento. */
|
||||
lookups?: LookupColumns;
|
||||
}): DocumentLayout {
|
||||
const columns = lineColumnsFor(opts.taxResource, opts.lineColumns);
|
||||
return {
|
||||
const layout: DocumentLayout = {
|
||||
lines: { key: 'DocumentLines', fields: lineFieldsOf(columns), base: true },
|
||||
header: opts.header ?? {
|
||||
left: [
|
||||
@@ -139,6 +189,7 @@ export function marketingLayout(opts: {
|
||||
recalc: recalcLine,
|
||||
requiredLine: [],
|
||||
};
|
||||
return opts.lookups ? withLookupColumns(layout, opts.lookups, 'replace') : layout;
|
||||
}
|
||||
|
||||
// --- consultas sobre un layout --------------------------------------------------------------
|
||||
|
||||
@@ -81,6 +81,7 @@ describe('formulario con campos de cliente', () => {
|
||||
],
|
||||
headerRight: [{ type: 'text', key: 'U_Obra', label: 'Obra' }],
|
||||
lineColumns: [{ key: 'U_Lote', title: 'Lote' }],
|
||||
lookupColumns: { partner: [{ key: 'U_Zona', title: 'Zona' }], item: [{ key: 'U_Marca', title: 'Marca' }] },
|
||||
},
|
||||
}),
|
||||
'/ventas/ordenes',
|
||||
@@ -127,6 +128,19 @@ describe('formulario con campos de cliente', () => {
|
||||
expect([...el().querySelectorAll<HTMLInputElement>('input')].map((i) => i.value)).toEqual(expect.arrayContaining(['Torre 3', 'L-77']));
|
||||
});
|
||||
|
||||
it('la ventana de artículos de una línea muestra las columnas del documento', async () => {
|
||||
await open(order);
|
||||
const itemBtn = el().querySelector('.pick button') as HTMLButtonElement;
|
||||
itemBtn.click();
|
||||
await tick();
|
||||
const req = ctrl.expectOne((r) => r.url === '/api/sl/Items');
|
||||
expect(req.request.params.get('$select')).toContain('U_Marca');
|
||||
req.flush({ value: [{ ItemCode: 'A1', ItemName: 'Pieza', U_Marca: 'Acme' }], 'odata.count': '1' });
|
||||
await tick();
|
||||
expect([...el().querySelectorAll('.dlg thead th')].map((t) => t.textContent?.trim())).toEqual(expect.arrayContaining(['Código', 'Descripción', 'Marca']));
|
||||
expect(el().querySelector('.dlg tbody')!.textContent).toContain('Acme');
|
||||
});
|
||||
|
||||
it('al modificar envía solo el campo de usuario que cambió', async () => {
|
||||
await open(order);
|
||||
page().setHeader('U_Canal', 'T');
|
||||
@@ -154,3 +168,57 @@ describe('formulario con campos de cliente', () => {
|
||||
ctrl.expectOne('/api/sl/Orders/5').flush(order);
|
||||
});
|
||||
});
|
||||
|
||||
describe('columnas de las listas de valores por documento', () => {
|
||||
const find = (screens: readonly { path: string }[], path: string) => by(screens, path).layout;
|
||||
const itemCols = (path: string, screens = BASE_SCREENS) =>
|
||||
find(screens, path).columns.find((c) => c.key === 'ItemCode')!.lookup!.config.columns.map((c) => c.key);
|
||||
|
||||
it('por defecto todos los documentos muestran código y descripción del artículo', () => {
|
||||
expect(itemCols('/ventas/ordenes')).toEqual(['ItemCode', 'ItemName']);
|
||||
expect(find(BASE_SCREENS, '/ventas/ordenes').lookups).toBeUndefined();
|
||||
});
|
||||
|
||||
it('una pantalla del base cambia las columnas de su lista sin afectar a las demás', async () => {
|
||||
const { marketingLayout } = await import('./document-layouts');
|
||||
const layout = marketingLayout({
|
||||
partnerLabel: 'Cliente',
|
||||
taxResource: 'SalesTaxCodes',
|
||||
lookups: {
|
||||
item: [{ key: 'ItemCode', title: 'Código' }, { key: 'ItemName', title: 'Descripción' }, { key: 'QuantityOnStock', title: 'Stock', type: 'number' }],
|
||||
partner: [{ key: 'CardCode', title: 'Código' }, { key: 'CardName', title: 'Nombre' }, { key: 'Phone1', title: 'Teléfono' }],
|
||||
},
|
||||
});
|
||||
expect(layout.columns.find((c) => c.key === 'ItemCode')!.lookup!.config.columns.map((c) => c.key)).toEqual(['ItemCode', 'ItemName', 'QuantityOnStock']);
|
||||
expect(layout.lookups!.partner!.map((c) => c.key)).toEqual(['CardCode', 'CardName', 'Phone1']);
|
||||
// el recurso, el filtro y los campos extra de la lista no cambian
|
||||
const cfg = layout.columns.find((c) => c.key === 'ItemCode')!.lookup!.config;
|
||||
expect(cfg.resource).toBe('Items');
|
||||
expect(cfg.extraFields).toEqual(expect.arrayContaining(['UoMGroupEntry']));
|
||||
expect(itemCols('/ventas/facturas')).toEqual(['ItemCode', 'ItemName']);
|
||||
});
|
||||
|
||||
it('una extensión de cliente suma columnas al final de las del base', () => {
|
||||
const out = applyExtensions(BASE_SCREENS, {
|
||||
'/ventas/ordenes': {
|
||||
lookupColumns: {
|
||||
item: [{ key: 'U_Marca', title: 'Marca' }],
|
||||
partner: [{ key: 'U_Zona', title: 'Zona' }],
|
||||
warehouse: [{ key: 'U_Sucursal', title: 'Sucursal' }],
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(itemCols('/ventas/ordenes', out)).toEqual(['ItemCode', 'ItemName', 'U_Marca']);
|
||||
const l = find(out, '/ventas/ordenes');
|
||||
expect(l.lookups!.partner!.map((c) => c.key)).toEqual(['CardCode', 'CardName', 'U_Zona']);
|
||||
expect(l.columns.find((c) => c.key === 'WarehouseCode')!.lookup!.config.columns.map((c) => c.key)).toEqual(['WarehouseCode', 'WarehouseName', 'U_Sucursal']);
|
||||
expect(itemCols('/ventas/facturas', out)).toEqual(['ItemCode', 'ItemName']);
|
||||
});
|
||||
|
||||
it('la lista de almacenes se aplica a las dos columnas de almacén de una transferencia', () => {
|
||||
const out = applyExtensions(BASE_SCREENS, { '/inventario/transferencias': { lookupColumns: { warehouse: [{ key: 'U_Sucursal', title: 'Sucursal' }] } } });
|
||||
const cols = find(out, '/inventario/transferencias').columns.filter((c) => c.lookup && /Warehouse/.test(c.key));
|
||||
expect(cols).toHaveLength(2);
|
||||
for (const c of cols) expect(c.lookup!.config.columns.at(-1)!.key).toBe('U_Sucursal');
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { GridColumn } from '../../shared/grid/grid.types';
|
||||
import type { ListColumn } from '../../shared/list/list.types';
|
||||
import type { DocumentLayout, FieldColumns, HeaderField } from './document-layouts';
|
||||
import { type DocumentLayout, type FieldColumns, type HeaderField, type LookupColumns, withLookupColumns } from './document-layouts';
|
||||
import type { DocumentScreen } from './screen-kit';
|
||||
import { lineFieldsOf } from './line-columns';
|
||||
|
||||
@@ -18,6 +18,8 @@ export interface ScreenExtension {
|
||||
lineColumns?: readonly GridColumn[];
|
||||
/** Columnas nuevas de la lista, al final. */
|
||||
listColumns?: readonly ListColumn[];
|
||||
/** Columnas que se suman al final de las listas de valores (ventanas de selección) de este documento. */
|
||||
lookupColumns?: LookupColumns;
|
||||
}
|
||||
|
||||
/** Extensiones por ruta de la pantalla (`/ventas/facturas`). */
|
||||
@@ -34,13 +36,14 @@ function extendLayout(layout: DocumentLayout, ext: ScreenExtension): DocumentLay
|
||||
const columns =
|
||||
at < 0 ? [...layout.columns, ...extra] : [...layout.columns.slice(0, at), ...extra, ...layout.columns.slice(at)];
|
||||
const fields = [...new Set([...layout.lines.fields, ...lineFieldsOf(extra)])];
|
||||
return {
|
||||
const extended: DocumentLayout = {
|
||||
...layout,
|
||||
header: withFields(layout.header, ext.headerLeft, ext.headerRight),
|
||||
footer: withFields(layout.footer, ext.footerLeft, ext.footerRight),
|
||||
columns,
|
||||
lines: { ...layout.lines, fields },
|
||||
};
|
||||
return ext.lookupColumns ? withLookupColumns(extended, ext.lookupColumns, 'add') : extended;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,7 @@ import { formatNumber } from '../../core/format';
|
||||
import type { ListFilter } from '../../core/sl/list-filters';
|
||||
import type { ListColumn, ListRow, Tone } from '../../shared/list/list.types';
|
||||
import type { GridColumn } from '../../shared/grid/grid.types';
|
||||
import { type DocumentLayout, FIELDS, type FieldColumns, type HeaderField, marketingLayout } from './document-layouts';
|
||||
import { type DocumentLayout, FIELDS, type FieldColumns, type HeaderField, type LookupColumns, marketingLayout } from './document-layouts';
|
||||
|
||||
/** Pantalla de un documento: lista de los últimos ingresados + formulario (nuevo / ver-editar). */
|
||||
export interface DocumentScreen {
|
||||
@@ -118,6 +118,8 @@ export interface MarketingExtras {
|
||||
header?: FieldColumns;
|
||||
/** Columnas de línea de este documento (de `LINE_COLUMNS`); sin ellas, todas. */
|
||||
lineColumns?: readonly GridColumn[];
|
||||
/** Columnas de las listas de valores de este documento (cliente, artículo, almacén, impuesto). */
|
||||
lookups?: LookupColumns;
|
||||
}
|
||||
|
||||
/** Documentos tributarios: la cabecera suma el folio (en el Service Layer `FolioPrefixString` y `FolioNumber`). */
|
||||
@@ -139,6 +141,7 @@ export const marketing = (
|
||||
right: extras.headerRight,
|
||||
header: extras.header,
|
||||
lineColumns: extras.lineColumns,
|
||||
lookups: extras.lookups,
|
||||
}),
|
||||
resource,
|
||||
createLabel,
|
||||
|
||||
Reference in new issue
Block a user