feat(web): extensiones por cliente (campos estándar adicionales y U_*) por módulo, y campo de selección
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1f212e2b30
commit
b3b47c34a3
12 files changed
+326
-10
No files matched your search
@@ -0,0 +1,156 @@
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
|
||||
import { type ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ActivatedRoute, Router, convertToParamMap, provideRouter } from '@angular/router';
|
||||
import { of } from 'rxjs';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { CLIENT_EXTENSIONS } from './client-extensions';
|
||||
import { headerKeysOf } from './document-layouts';
|
||||
import { DocumentFormPage } from './document-form.page';
|
||||
import { BASE_SCREENS, DOCUMENT_SCREENS } from './document-screens';
|
||||
import { type ScreenExtensions, applyExtensions } from './screen-extensions';
|
||||
|
||||
const by = (screens: readonly { path: string }[], path: string) => screens.find((s) => s.path === path)! as (typeof BASE_SCREENS)[number];
|
||||
|
||||
const EXT: ScreenExtensions = {
|
||||
'/ventas/facturas': {
|
||||
headerRight: [{ type: 'text', key: 'U_Obra', label: 'Obra' }],
|
||||
footerLeft: [{ type: 'text', key: 'U_Nota', label: 'Nota interna' }],
|
||||
lineColumns: [{ key: 'U_Lote', title: 'Lote', width: '90px' }],
|
||||
listColumns: [{ key: 'U_Obra', title: 'Obra', searchable: true }],
|
||||
},
|
||||
'/inventario/entradas': { headerLeft: [{ type: 'text', key: 'U_Chofer', label: 'Chofer' }], lineColumns: [{ key: 'U_Origen', title: 'Origen' }] },
|
||||
};
|
||||
|
||||
describe('extensiones por cliente', () => {
|
||||
it('en main no agregan nada: las pantallas son las del base', () => {
|
||||
expect(CLIENT_EXTENSIONS).toEqual({});
|
||||
expect(DOCUMENT_SCREENS).toEqual(BASE_SCREENS);
|
||||
});
|
||||
|
||||
it('agregan campos de cabecera y de pie al final y los envían a SAP', () => {
|
||||
const s = by(applyExtensions(BASE_SCREENS, EXT), '/ventas/facturas');
|
||||
expect(s.layout.header.right.at(-1)).toMatchObject({ key: 'U_Obra' });
|
||||
expect(s.layout.footer.left.at(-1)).toMatchObject({ key: 'U_Nota' });
|
||||
expect(headerKeysOf(s.layout)).toEqual(expect.arrayContaining(['U_Obra', 'U_Nota']));
|
||||
});
|
||||
|
||||
it('agregan columnas de línea antes del total y las envían; y columnas de lista al final', () => {
|
||||
const s = by(applyExtensions(BASE_SCREENS, EXT), '/ventas/facturas');
|
||||
const keys = s.layout.columns.map((c) => c.key);
|
||||
expect(keys.indexOf('U_Lote')).toBe(keys.indexOf('LineTotal') - 1);
|
||||
expect(s.layout.lines.fields).toContain('U_Lote');
|
||||
expect(s.columns.at(-1)).toMatchObject({ key: 'U_Obra' });
|
||||
});
|
||||
|
||||
it('en un documento sin total las columnas van al final', () => {
|
||||
const s = by(applyExtensions(BASE_SCREENS, EXT), '/inventario/entradas');
|
||||
expect(s.layout.columns.at(-1)).toMatchObject({ key: 'U_Origen' });
|
||||
});
|
||||
|
||||
it('solo cambia la pantalla extendida (aunque comparta layout con otra)', () => {
|
||||
const out = applyExtensions(BASE_SCREENS, EXT);
|
||||
expect(by(out, '/inventario/salidas')).toEqual(by(BASE_SCREENS, '/inventario/salidas'));
|
||||
expect(by(out, '/ventas/ordenes')).toEqual(by(BASE_SCREENS, '/ventas/ordenes'));
|
||||
expect(by(BASE_SCREENS, '/ventas/facturas').layout.columns.map((c) => c.key)).not.toContain('U_Lote');
|
||||
});
|
||||
|
||||
it('una ruta que no existe es un error', () => {
|
||||
expect(() => applyExtensions(BASE_SCREENS, { '/ventas/facturaz': { listColumns: [] } })).toThrow(/no existe la pantalla/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('formulario con campos de cliente', () => {
|
||||
let fixture: ComponentFixture<DocumentFormPage>;
|
||||
let ctrl: HttpTestingController;
|
||||
const el = () => fixture.nativeElement as HTMLElement;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const page = () => fixture.componentInstance as any;
|
||||
const tick = async () => {
|
||||
fixture.detectChanges();
|
||||
await new Promise((r) => setTimeout(r));
|
||||
fixture.detectChanges();
|
||||
};
|
||||
|
||||
async function open(doc: Record<string, unknown>) {
|
||||
const screen = by(
|
||||
applyExtensions(BASE_SCREENS, {
|
||||
'/ventas/ordenes': {
|
||||
headerLeft: [
|
||||
{ type: 'select', key: 'U_Canal', label: 'Canal', options: [{ value: 'W', label: 'Web' }, { value: 'T', label: 'Tienda' }] },
|
||||
],
|
||||
headerRight: [{ type: 'text', key: 'U_Obra', label: 'Obra' }],
|
||||
lineColumns: [{ key: 'U_Lote', title: 'Lote' }],
|
||||
},
|
||||
}),
|
||||
'/ventas/ordenes',
|
||||
);
|
||||
const route = {
|
||||
snapshot: { data: { screen, title: 'Órdenes de venta' }, paramMap: convertToParamMap({ docEntry: '5' }) },
|
||||
paramMap: of(convertToParamMap({ docEntry: '5' })),
|
||||
};
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [DocumentFormPage],
|
||||
providers: [provideRouter([]), provideHttpClient(), provideHttpClientTesting(), { provide: ActivatedRoute, useValue: route }],
|
||||
}).compileComponents();
|
||||
ctrl = TestBed.inject(HttpTestingController);
|
||||
vi.spyOn(TestBed.inject(Router), 'navigate').mockResolvedValue(true);
|
||||
fixture = TestBed.createComponent(DocumentFormPage);
|
||||
await tick();
|
||||
ctrl.expectOne((r) => r.url === '/api/sl/SalesTaxCodes').flush({ value: [] });
|
||||
ctrl.expectOne('/api/sl/Orders/5').flush(doc);
|
||||
await tick();
|
||||
}
|
||||
|
||||
const order = {
|
||||
'odata.etag': 'W/"1"',
|
||||
DocEntry: 5,
|
||||
DocNum: 5,
|
||||
CardCode: 'C1',
|
||||
CardName: 'ACME',
|
||||
DocDate: '2026-09-11T00:00:00Z',
|
||||
DocDueDate: '2026-09-20T00:00:00Z',
|
||||
DiscountPercent: 0,
|
||||
DocumentStatus: 'bost_Open',
|
||||
Cancelled: 'tNO',
|
||||
U_Canal: 'W',
|
||||
U_Obra: 'Torre 3',
|
||||
DocumentLines: [{ LineNum: 0, ItemCode: 'A1', ItemDescription: 'Pieza', Quantity: 1, Price: 10, DiscountPercent: 0, TaxCode: '', LineTotal: 10, WarehouseCode: '01', U_Lote: 'L-77' }],
|
||||
};
|
||||
|
||||
beforeEach(() => TestBed.resetTestingModule());
|
||||
|
||||
it('muestra el campo de usuario, el desplegable y la columna de línea con sus valores de SAP', async () => {
|
||||
await open(order);
|
||||
expect([...el().querySelectorAll('label')].map((l) => l.textContent?.trim())).toEqual(expect.arrayContaining(['Canal', 'Obra']));
|
||||
expect(el().querySelector<HTMLSelectElement>('select')!.value).toBe('W');
|
||||
expect([...el().querySelectorAll<HTMLInputElement>('input')].map((i) => i.value)).toEqual(expect.arrayContaining(['Torre 3', 'L-77']));
|
||||
});
|
||||
|
||||
it('al modificar envía solo el campo de usuario que cambió', async () => {
|
||||
await open(order);
|
||||
page().setHeader('U_Canal', 'T');
|
||||
page().setHeader('U_Obra', 'Torre 4');
|
||||
await tick();
|
||||
(el().querySelector('.actions .btn.main') as HTMLElement).click();
|
||||
await tick();
|
||||
const req = ctrl.expectOne((r) => r.method === 'PATCH');
|
||||
expect(req.request.body).toEqual({ U_Canal: 'T', U_Obra: 'Torre 4' });
|
||||
req.flush(null);
|
||||
await tick();
|
||||
ctrl.expectOne('/api/sl/Orders/5').flush(order);
|
||||
});
|
||||
|
||||
it('al cambiar la columna de línea envía las líneas con el campo de usuario', async () => {
|
||||
await open(order);
|
||||
page().setLines([{ ...page().lines()[0], U_Lote: 'L-80' }]);
|
||||
await tick();
|
||||
(el().querySelector('.actions .btn.main') as HTMLElement).click();
|
||||
await tick();
|
||||
const req = ctrl.expectOne((r) => r.method === 'PATCH');
|
||||
expect((req.request.body as { DocumentLines: Record<string, unknown>[] }).DocumentLines[0]).toMatchObject({ ItemCode: 'A1', U_Lote: 'L-80' });
|
||||
req.flush(null);
|
||||
await tick();
|
||||
ctrl.expectOne('/api/sl/Orders/5').flush(order);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user