feat(web): el indicador del documento se elige de su ventana de selección (FactoringIndicators)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
02c231a662
commit
d1cafcd721
6 files changed
+56
-4
No files matched your search
@@ -60,7 +60,7 @@ Pendiente, en orden:
|
||||
|
||||
**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 al final y se envían a SAP) y `listColumns`. Además de agregar, la extensión puede **ocultar** (`hideHeaderFields`, `hideLineColumns`, `hideListColumns`; en maestros `hideFields`, `hideListColumns`) y dejar de **solo lectura** (`readOnlyHeaderFields`, `readOnlyLineColumns`; en maestros `readOnlyFields`): lo oculto ni se muestra ni se envía; lo de solo lectura se ve pero no se edita y su valor sigue enviándose (columna `locked`, a diferencia de `readonly`, que no se envía). Los nombres son los de SAP (`CostingCode2`, `Indicator`…); un nombre inexistente, ocultar un campo/columna obligatorio, o una ruta inexistente o ajena al módulo lanzan 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). La alineación de una columna se declara con `align: 'left' | 'center' | 'right'` (listas y detalle; por defecto, derecha en las numéricas).
|
||||
|
||||
**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. **Campos de contabilidad analítica y numeración (estándar):** cabecera de todos los documentos: `Series` (la fila «Número» lleva la lista de series del documento, pedida con la acción `SeriesService_GetDocumentSeries` del Service Layer —registrada en el proxy como acción de solo consulta—, filtrada por el subtipo de la pantalla; no se puede cambiar una vez creado; sin elegir, SAP usa la por defecto), `Indicator` y `Project` (lista de proyectos). Detalle de ventas y compras: `PriceAfterVAT` (el precio con IVA; solo se envía a SAP el que el usuario escribió, `Price` o `PriceAfterVAT`, nunca los dos), `CostingCode` a `CostingCode5` y `ProjectCode` (cada uno con su **ventana modal** de selección: reglas de distribución por dimensión y proyectos), `AccountCode` (plan de cuentas) y `BaseType`/`BaseEntry`/`BaseLine` (solo lectura: el documento de origen de una línea copiada). Inventario y solicitudes de compra llevan en el detalle solo las cinco dimensiones y el proyecto. `OBJECT_TYPES` (`document-objects.ts`) da el tipo de objeto de SAP de cada pantalla. El subtipo de las series de boleta (`IB`) y el resto de `SERIES_SUBTYPE` no está verificado contra SBODemoCL (solo hay series `--`).
|
||||
**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. **Campos de contabilidad analítica y numeración (estándar):** cabecera de todos los documentos: `Series` (la fila «Número» lleva la lista de series del documento, pedida con la acción `SeriesService_GetDocumentSeries` del Service Layer —registrada en el proxy como acción de solo consulta—, filtrada por el subtipo de la pantalla; no se puede cambiar una vez creado; sin elegir, SAP usa la por defecto), `Indicator` (ventana de selección sobre `FactoringIndicators` —tabla OIDC; en tu SAP: 33 Factura, 39 Boleta; el recurso `Indicators` no existe en este Service Layer) y `Project` (lista de proyectos). Detalle de ventas y compras: `PriceAfterVAT` (el precio con IVA; solo se envía a SAP el que el usuario escribió, `Price` o `PriceAfterVAT`, nunca los dos), `CostingCode` a `CostingCode5` y `ProjectCode` (cada uno con su **ventana modal** de selección: reglas de distribución por dimensión y proyectos), `AccountCode` (plan de cuentas) y `BaseType`/`BaseEntry`/`BaseLine` (solo lectura: el documento de origen de una línea copiada). Inventario y solicitudes de compra llevan en el detalle solo las cinco dimensiones y el proyecto. `OBJECT_TYPES` (`document-objects.ts`) da el tipo de objeto de SAP de cada pantalla. El subtipo de las series de boleta (`IB`) y el resto de `SERIES_SUBTYPE` no está verificado contra SBODemoCL (solo hay series `--`).
|
||||
|
||||
Flete hecho: fila «Flete» en el pie de ventas y compras (gasto adicional + importe; `freightPayload` lo guarda como `DocumentAdditionalExpenses`, reemplazando la colección; el catálogo `AdditionalExpenses` está registrado en el proxy; con varios gastos adicionales cargados en SAP solo se muestra). `freight: false` en `marketing(...)` lo quita de un documento. El toolbar del formulario ocupa todo el ancho del contenido (`<sbo-toolbar bleed>`). Falta: botón PDF. En ventas el flete falla en SBODemoCL ("Define the freight revenue account…"): el gasto adicional «Flete» solo tiene cuenta de gasto, no de ingresos (configuración de SAP, no del código); en compras funciona (orden de compra 2885).
|
||||
2. ~~Pantallas de maestros~~ — hechas (clientes, proveedores y artículos): `features/masters/`, con la misma idea que los documentos pero con clave de texto (`CardCode`, `ItemCode`). Cada pantalla declara su `MasterScreen` (`socios-negocio/socios.screens.ts`, `articulos/articulos.screens.ts`): columnas y filtros de la lista, `baseFilter` (clientes = `CardType eq 'cCustomer'`, proveedores = `cSupplier`; el tipo también se envía al crear con `fixed`), y un `MasterLayout` con código/nombre/estado arriba y los demás campos en **pestañas** (dos columnas, tipos de `HeaderField`: texto, número, fecha, área de texto, casilla `tYES/tNO`, lista de valores numérica o de texto, almacén). El código no se cambia una vez creado (`immutable`); «Inactivo» es `Frozen`. Rutas con `master-paths.ts` + `resolve` diferido; navegación primero/anterior/siguiente/último por clave de texto (`SlMasterApi`). Los campos por cliente van en `socios-negocio/socios.extensions.ts` y `articulos/articulos.extensions.ts` (vacíos en `main`; `header`, `tabs` por `id`, `newTabs`, `listColumns`, y también `hideFields`, `readOnlyFields`, `hideListColumns`; pestaña, campo o ruta inexistente = error). Pendiente en maestros: colecciones (direcciones, personas de contacto, precios por lista, almacenes por artículo), lotes/series, y cambiar el tipo de socio. Los catálogos `BusinessPartnerGroups` e `ItemGroups` están registrados en el proxy.
|
||||
|
||||
@@ -18,7 +18,7 @@ describe('recursos del Service Layer', () => {
|
||||
});
|
||||
|
||||
it('dimensiones, proyectos y cuentas son catálogos de solo lectura', () => {
|
||||
for (const name of ['DistributionRules', 'Projects', 'ChartOfAccounts', 'AdditionalExpenses', 'ItemGroups', 'BusinessPartnerGroups']) {
|
||||
for (const name of ['FactoringIndicators', 'DistributionRules', 'Projects', 'ChartOfAccounts', 'AdditionalExpenses', 'ItemGroups', 'BusinessPartnerGroups']) {
|
||||
const r = SL_RESOURCE_MAP.get(name)!;
|
||||
expect(r.methods, name).toEqual(['GET']);
|
||||
expect(r.read, name).toBeNull();
|
||||
|
||||
@@ -85,6 +85,8 @@ export const SL_RESOURCES: readonly SlResource[] = [
|
||||
catalog('BusinessPartnerGroups', 'number'),
|
||||
catalog('ItemGroups', 'number'),
|
||||
// Dimensiones, proyectos y cuentas de los documentos (para elegirlos en cabecera y líneas).
|
||||
// Indicadores de los documentos (tabla OIDC; en la localización chilena, el tipo de documento: 33 factura, 39 boleta).
|
||||
catalog('FactoringIndicators', 'string'),
|
||||
catalog('DistributionRules', 'string'),
|
||||
catalog('Projects', 'string'),
|
||||
catalog('ChartOfAccounts', 'string'),
|
||||
|
||||
@@ -12,6 +12,17 @@ export const dimensionLookup = (n: 1 | 2 | 3 | 4 | 5): VhLookup => ({
|
||||
],
|
||||
});
|
||||
|
||||
/** Indicadores del documento (`Indicator`): en la localización chilena, el tipo de documento (33 factura, 39 boleta). */
|
||||
export const INDICATOR_LOOKUP: VhLookup = {
|
||||
title: 'Indicadores',
|
||||
resource: 'FactoringIndicators',
|
||||
orderby: 'IndicatorCode',
|
||||
columns: [
|
||||
{ key: 'IndicatorCode', title: 'Código', width: '100px', mono: true },
|
||||
{ key: 'IndicatorName', title: 'Nombre' },
|
||||
],
|
||||
};
|
||||
|
||||
/** Proyectos activos. */
|
||||
export const PROJECT_LOOKUP: VhLookup = {
|
||||
title: 'Proyectos',
|
||||
|
||||
@@ -245,3 +245,42 @@ describe('el proyecto de la cabecera se elige de su ventana modal', () => {
|
||||
expect(fixture.componentInstance['header']()['Project']).toBe('PE001');
|
||||
});
|
||||
});
|
||||
|
||||
describe('el indicador de la cabecera se elige de su ventana modal', () => {
|
||||
it('es un campo con lista de valores sobre FactoringIndicators, con código y nombre', () => {
|
||||
const f = DOCUMENT_SCREENS.find((s) => s.path === '/ventas/facturas')!.layout.header.left.find((x) => x.type === 'lookup' && x.key === 'Indicator');
|
||||
expect(f && f.type === 'lookup' && f.lookup.resource).toBe('FactoringIndicators');
|
||||
expect(f && f.type === 'lookup' && f.lookup.columns.map((c) => c.key)).toEqual(['IndicatorCode', 'IndicatorName']);
|
||||
});
|
||||
|
||||
it('abre su ventana de selección y escribe el código elegido', async () => {
|
||||
TestBed.resetTestingModule();
|
||||
const screen = DOCUMENT_SCREENS.find((s) => s.path === '/ventas/facturas')!;
|
||||
const route = {
|
||||
snapshot: { data: { screen, title: 'Documento' }, paramMap: convertToParamMap({}) },
|
||||
paramMap: of(convertToParamMap({})),
|
||||
};
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [DocumentFormPage],
|
||||
providers: [provideRouter([]), provideHttpClient(), provideHttpClientTesting(), { provide: ActivatedRoute, useValue: route }],
|
||||
}).compileComponents();
|
||||
const ctrl = TestBed.inject(HttpTestingController);
|
||||
const fixture = TestBed.createComponent(DocumentFormPage);
|
||||
const tick = async () => {
|
||||
fixture.detectChanges();
|
||||
await new Promise((r) => setTimeout(r));
|
||||
fixture.detectChanges();
|
||||
};
|
||||
await tick();
|
||||
const el = fixture.nativeElement as HTMLElement;
|
||||
const row = [...el.querySelectorAll('sbo-lookup-field')].find((f) => f.querySelector('label')?.textContent?.trim() === 'Indicador')!;
|
||||
expect(row, 'campo Indicador con lista').toBeDefined();
|
||||
(row.querySelector('button.sbo-btn') as HTMLElement).click();
|
||||
await tick();
|
||||
ctrl.expectOne((r) => r.url === '/api/sl/FactoringIndicators').flush({ value: [{ IndicatorCode: '33', IndicatorName: 'Factura' }, { IndicatorCode: '39', IndicatorName: 'Boleta' }], 'odata.count': '2' });
|
||||
await tick();
|
||||
(el.querySelectorAll('.dlg tbody tr')[1] as HTMLElement).click();
|
||||
await tick();
|
||||
expect(fixture.componentInstance['header']()['Indicator']).toBe('39');
|
||||
});
|
||||
});
|
||||
@@ -2,7 +2,7 @@ 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 { PROJECT_LOOKUP } from './dimension-lookups';
|
||||
import { INDICATOR_LOOKUP, PROJECT_LOOKUP } from './dimension-lookups';
|
||||
import { SERIES_SUBTYPE } from './document-objects';
|
||||
import { ANALYTIC_COLUMNS, DOCUMENT_LINE_COLUMNS, lineColumnsFor, lineFieldsOf, newDocumentLine, recalcLine, uomEntryOf } from './line-columns';
|
||||
|
||||
@@ -95,7 +95,7 @@ export const FIELDS = {
|
||||
docNum: { type: 'computed', label: 'Número', source: 'docNum' },
|
||||
/** Número del documento con su serie. */
|
||||
docNumber: { type: 'series', key: 'Series', label: 'Número' },
|
||||
indicator: { type: 'text', key: 'Indicator', label: 'Indicador' },
|
||||
indicator: { type: 'lookup', key: 'Indicator', label: 'Indicador', lookup: INDICATOR_LOOKUP },
|
||||
project: { type: 'lookup', key: 'Project', label: 'Proyecto', lookup: PROJECT_LOOKUP },
|
||||
docDate: { type: 'date', key: 'DocDate', label: 'Fecha de contabilización', required: true, missing: 'la fecha de contabilización' },
|
||||
docDueDate: { type: 'date', key: 'DocDueDate', label: 'Fecha de vencimiento' },
|
||||
|
||||
Reference in new issue
Block a user