feat(web): las extensiones de cliente pueden ocultar campos y columnas y dejarlos de solo lectura (documentos y maestros)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f077a61d08
commit
8dc4a8eaab
20 files changed
+374
-38
No files matched your search
@@ -6,6 +6,7 @@ import { of } from 'rxjs';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { CLIENT_EXTENSIONS } from './client-extensions';
|
||||
import { headerKeysOf } from './document-layouts';
|
||||
import { lineFieldsOf } from './line-columns';
|
||||
import { DocumentFormPage } from './document-form.page';
|
||||
import { BASE_SCREENS, DOCUMENT_SCREENS } from './document-screens';
|
||||
import { type ScreenExtensions, applyExtensions } from './screen-extensions';
|
||||
@@ -224,3 +225,122 @@ describe('columnas de las listas de valores por documento', () => {
|
||||
for (const c of cols) expect(c.lookup!.config.columns.at(-1)!.key).toBe('U_Sucursal');
|
||||
});
|
||||
});
|
||||
|
||||
describe('ocultar y dejar de solo lectura (extensiones de cliente)', () => {
|
||||
const layoutOf = (out: readonly { path: string }[], path: string) => by(out, path).layout;
|
||||
const apply = (e: ScreenExtensions) => applyExtensions(BASE_SCREENS, e);
|
||||
const DIMS_2_5 = ['CostingCode2', 'CostingCode3', 'CostingCode4', 'CostingCode5'];
|
||||
|
||||
it('un cliente que solo quiere CostingCode oculta las otras dimensiones: no se muestran ni se envían', () => {
|
||||
const l = layoutOf(apply({ '/ventas/facturas': { hideLineColumns: DIMS_2_5 } }), '/ventas/facturas');
|
||||
const keys = l.columns.map((c) => c.key);
|
||||
expect(keys).toContain('CostingCode');
|
||||
for (const k of DIMS_2_5) {
|
||||
expect(keys).not.toContain(k);
|
||||
expect(l.lines.fields).not.toContain(k);
|
||||
}
|
||||
expect(l.lines.fields).toContain('CostingCode');
|
||||
});
|
||||
|
||||
it('oculta también las columnas de lectura y de lista, sin tocar las demás pantallas', () => {
|
||||
const out = apply({
|
||||
'/ventas/facturas': { hideLineColumns: ['BaseType', 'BaseEntry', 'BaseLine', 'AccountCode'], hideListColumns: ['FolioPrefixString'] },
|
||||
});
|
||||
expect(layoutOf(out, '/ventas/facturas').columns.map((c) => c.key)).not.toEqual(expect.arrayContaining(['BaseType']));
|
||||
expect(by(out, '/ventas/facturas').columns.map((c) => c.key)).not.toContain('FolioPrefixString');
|
||||
expect(by(out, '/ventas/facturas').columns.map((c) => c.key)).toContain('FolioNumber');
|
||||
expect(layoutOf(out, '/ventas/ordenes').columns.map((c) => c.key)).toEqual(layoutOf(BASE_SCREENS, '/ventas/ordenes').columns.map((c) => c.key));
|
||||
});
|
||||
|
||||
it('oculta campos de cabecera: dejan de mostrarse y de enviarse', () => {
|
||||
const l = layoutOf(apply({ '/ventas/ordenes': { hideHeaderFields: ['Indicator', 'Project', 'partnerTaxId'] } }), '/ventas/ordenes');
|
||||
const keys = headerKeysOf(l);
|
||||
expect(keys).not.toEqual(expect.arrayContaining(['Indicator']));
|
||||
expect(keys).not.toContain('Project');
|
||||
expect(l.header.left.map((f) => ('label' in f ? f.label : ''))).not.toContain('RUT');
|
||||
expect(keys).toContain('NumAtCard');
|
||||
});
|
||||
|
||||
it('el cliente puede ocultar una columna base y agregar la suya', () => {
|
||||
const l = layoutOf(apply({ '/ventas/ordenes': { hideLineColumns: ['AccountCode'], lineColumns: [{ key: 'U_Lote', title: 'Lote' }] } }), '/ventas/ordenes');
|
||||
const keys = l.columns.map((c) => c.key);
|
||||
expect(keys).not.toContain('AccountCode');
|
||||
expect(keys.at(-1)).toBe('U_Lote');
|
||||
});
|
||||
|
||||
it('una columna de solo lectura se ve y no se edita, pero su valor sigue enviándose', () => {
|
||||
const l = layoutOf(apply({ '/ventas/facturas': { readOnlyLineColumns: ['CostingCode', 'ProjectCode'] } }), '/ventas/facturas');
|
||||
const col = (k: string) => l.columns.find((c) => c.key === k)!;
|
||||
expect(col('CostingCode').locked).toBe(true);
|
||||
expect(col('ProjectCode').locked).toBe(true);
|
||||
expect(col('CostingCode2').locked).toBeUndefined();
|
||||
expect(l.lines.fields).toEqual(expect.arrayContaining(['CostingCode', 'ProjectCode']));
|
||||
expect(lineFieldsOf(l.columns)).toEqual(expect.arrayContaining(['CostingCode', 'ProjectCode']));
|
||||
});
|
||||
|
||||
it('un campo de cabecera de solo lectura queda marcado y se sigue enviando', () => {
|
||||
const l = layoutOf(apply({ '/ventas/ordenes': { readOnlyHeaderFields: ['Indicator', 'NumAtCard'] } }), '/ventas/ordenes');
|
||||
expect(l.lockedKeys).toEqual(expect.arrayContaining(['Indicator', 'NumAtCard']));
|
||||
expect(headerKeysOf(l)).toEqual(expect.arrayContaining(['Indicator', 'NumAtCard']));
|
||||
});
|
||||
|
||||
it('no se puede ocultar lo obligatorio, y un nombre que no existe es un error', () => {
|
||||
expect(() => apply({ '/ventas/ordenes': { hideLineColumns: ['ItemCode'] } })).toThrow(/obligatoria/);
|
||||
expect(() => apply({ '/ventas/ordenes': { hideLineColumns: ['Quantity'] } })).toThrow(/obligatoria/);
|
||||
expect(() => apply({ '/ventas/ordenes': { hideHeaderFields: ['CardCode'] } })).toThrow(/obligatorio/);
|
||||
expect(() => apply({ '/inventario/entradas': { hideLineColumns: ['WarehouseCode'] } })).toThrow(/obligatoria/);
|
||||
expect(() => apply({ '/ventas/ordenes': { hideLineColumns: ['CostingCode9'] } })).toThrow(/no tiene la columna de línea/);
|
||||
expect(() => apply({ '/ventas/ordenes': { hideHeaderFields: ['Nada'] } })).toThrow(/no tiene el campo de cabecera/);
|
||||
expect(() => apply({ '/ventas/ordenes': { readOnlyLineColumns: ['Nada'] } })).toThrow(/no tiene la columna de línea/);
|
||||
expect(() => apply({ '/ventas/ordenes': { hideListColumns: ['Nada'] } })).toThrow(/no tiene la columna de lista/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('formulario con campos ocultos o de solo lectura', () => {
|
||||
let fixture: ComponentFixture<DocumentFormPage>;
|
||||
const el = () => fixture.nativeElement as HTMLElement;
|
||||
const tick = async () => {
|
||||
fixture.detectChanges();
|
||||
await new Promise((r) => setTimeout(r));
|
||||
fixture.detectChanges();
|
||||
};
|
||||
|
||||
async function openWith(ext: ScreenExtensions) {
|
||||
TestBed.resetTestingModule();
|
||||
const screen = by(applyExtensions(BASE_SCREENS, ext), '/ventas/ordenes');
|
||||
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();
|
||||
fixture = TestBed.createComponent(DocumentFormPage);
|
||||
await tick();
|
||||
}
|
||||
const input = (label: string) => {
|
||||
const f = [...el().querySelectorAll('sbo-field, sbo-text-field, sbo-lookup-field')].find((x) => x.querySelector('label')?.textContent?.trim() === label);
|
||||
return f?.querySelector<HTMLInputElement>('input') ?? null;
|
||||
};
|
||||
|
||||
it('un campo oculto no aparece en el formulario', async () => {
|
||||
await openWith({ '/ventas/ordenes': { hideHeaderFields: ['Indicator'] } });
|
||||
expect([...el().querySelectorAll('label')].map((l) => l.textContent?.trim())).not.toContain('Indicador');
|
||||
expect([...el().querySelectorAll('label')].map((l) => l.textContent?.trim())).toContain('Proyecto');
|
||||
});
|
||||
|
||||
it('un campo de solo lectura se ve pero no se puede escribir', async () => {
|
||||
await openWith({ '/ventas/ordenes': { readOnlyHeaderFields: ['Indicator', 'Project'] } });
|
||||
expect(input('Indicador')!.readOnly).toBe(true);
|
||||
expect(input('Proyecto')!.readOnly).toBe(true);
|
||||
expect(input('Nº referencia')!.readOnly).toBe(false);
|
||||
const buttons = [...el().querySelectorAll('sbo-lookup-field')].find((f) => f.querySelector('label')?.textContent?.trim() === 'Proyecto')!.querySelector('button.sbo-btn') as HTMLButtonElement;
|
||||
expect(buttons.disabled).toBe(true);
|
||||
});
|
||||
|
||||
it('sin extensiones los campos se editan como siempre', async () => {
|
||||
await openWith({});
|
||||
expect(input('Indicador')!.readOnly).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user