2026-10-13 22:00:54 -05:00
|
|
|
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 { DocumentFormPage } from './document-form.page';
|
|
|
|
|
import { DOCUMENT_SCREENS } from './document-screens';
|
|
|
|
|
|
|
|
|
|
const screen = DOCUMENT_SCREENS.find((s) => s.resource === 'Orders')!;
|
|
|
|
|
|
|
|
|
|
const order = (over: Record<string, unknown> = {}) => ({
|
|
|
|
|
'odata.etag': 'W/"77"',
|
|
|
|
|
DocEntry: 41,
|
|
|
|
|
DocNum: 1041,
|
|
|
|
|
CardCode: 'C1',
|
|
|
|
|
CardName: 'ACME',
|
|
|
|
|
NumAtCard: '',
|
2026-10-14 09:13:53 -05:00
|
|
|
DocDate: '2026-09-11T00:00:00Z',
|
|
|
|
|
DocDueDate: '2026-09-20T00:00:00Z',
|
2026-10-13 22:00:54 -05:00
|
|
|
DiscountPercent: 0,
|
|
|
|
|
Comments: 'hola',
|
|
|
|
|
DocumentStatus: 'bost_Open',
|
|
|
|
|
Cancelled: 'tNO',
|
|
|
|
|
DocumentLines: [
|
|
|
|
|
{ LineNum: 0, ItemCode: 'A1', ItemDescription: 'Pieza', Quantity: 2, Price: 100, DiscountPercent: 0, TaxCode: 'IVA', LineTotal: 200, WarehouseCode: '01' },
|
|
|
|
|
],
|
|
|
|
|
...over,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('DocumentFormPage', () => {
|
|
|
|
|
let fixture: ComponentFixture<DocumentFormPage>;
|
|
|
|
|
let ctrl: HttpTestingController;
|
|
|
|
|
let navigate: ReturnType<typeof vi.spyOn>;
|
|
|
|
|
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();
|
|
|
|
|
};
|
2026-10-14 11:59:07 -05:00
|
|
|
const buttonText = () => [...el().querySelectorAll('.actions .start .btn')].map((b) => b.textContent?.trim());
|
2026-10-13 22:00:54 -05:00
|
|
|
|
2026-10-14 11:59:07 -05:00
|
|
|
async function open(docEntry: string | null, doc?: Record<string, unknown>, copyFrom?: unknown, afterCreate?: () => void) {
|
2026-10-13 22:00:54 -05:00
|
|
|
const params = docEntry ? { docEntry } : {};
|
|
|
|
|
const route = {
|
|
|
|
|
snapshot: { data: { screen, title: 'Órdenes de venta' }, paramMap: convertToParamMap(params) },
|
|
|
|
|
paramMap: of(convertToParamMap(params)),
|
|
|
|
|
};
|
|
|
|
|
await TestBed.configureTestingModule({
|
|
|
|
|
imports: [DocumentFormPage],
|
|
|
|
|
providers: [provideRouter([]), provideHttpClient(), provideHttpClientTesting(), { provide: ActivatedRoute, useValue: route }],
|
|
|
|
|
}).compileComponents();
|
|
|
|
|
ctrl = TestBed.inject(HttpTestingController);
|
2026-10-14 11:59:07 -05:00
|
|
|
const router = TestBed.inject(Router);
|
|
|
|
|
navigate = vi.spyOn(router, 'navigate').mockResolvedValue(true);
|
|
|
|
|
if (copyFrom) vi.spyOn(router, 'currentNavigation').mockReturnValue({ extras: { state: { copyFrom } } } as never);
|
2026-10-13 22:00:54 -05:00
|
|
|
fixture = TestBed.createComponent(DocumentFormPage);
|
|
|
|
|
await tick();
|
|
|
|
|
ctrl.expectOne('/api/sl/SalesTaxCodes?$select=Code,Rate&$top=500').flush({ value: [{ Code: 'IVA', Rate: 19 }] });
|
|
|
|
|
if (docEntry) ctrl.expectOne(`/api/sl/Orders/${docEntry}`).flush(doc ?? {});
|
2026-10-14 11:59:07 -05:00
|
|
|
afterCreate?.();
|
2026-10-13 22:00:54 -05:00
|
|
|
await tick();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
beforeEach(() => TestBed.resetTestingModule());
|
|
|
|
|
|
|
|
|
|
describe('documento abierto', () => {
|
|
|
|
|
beforeEach(() => open('41', order()));
|
|
|
|
|
|
|
|
|
|
it('muestra la cabecera, las líneas y los totales con la tasa del impuesto', () => {
|
|
|
|
|
const inputs = [...el().querySelectorAll('input')].map((i) => i.value);
|
|
|
|
|
expect(inputs).toContain('C1');
|
|
|
|
|
expect(inputs).toContain('ACME');
|
|
|
|
|
expect(el().querySelector('.badge.tone-ok')?.textContent?.trim()).toBe('Abierto');
|
|
|
|
|
expect(el().querySelector('h1')?.textContent).toContain('1041');
|
|
|
|
|
expect(el().querySelectorAll('tbody tr').length).toBeGreaterThanOrEqual(1);
|
2026-10-19 12:46:56 -05:00
|
|
|
expect(page().totals()).toEqual({ subtotal: 200, discount: 0, net: 200, tax: 38, expenses: 0, total: 238 });
|
2026-10-13 22:00:54 -05:00
|
|
|
expect(buttonText()).toContain('Actualizar');
|
|
|
|
|
});
|
|
|
|
|
|
2026-10-14 09:13:53 -05:00
|
|
|
it('las fechas de SAP (con hora) se muestran y se comparan como AAAA-MM-DD', () => {
|
|
|
|
|
expect(page().header().DocDate).toBe('2026-09-11');
|
|
|
|
|
expect(page().dirty()).toBe(false);
|
|
|
|
|
});
|
|
|
|
|
|
2026-10-13 22:00:54 -05:00
|
|
|
it('sin cambios, Actualizar no envía nada', async () => {
|
|
|
|
|
(el().querySelector('.actions .btn.main') as HTMLElement).click();
|
|
|
|
|
await tick();
|
|
|
|
|
ctrl.expectNone((r) => r.method === 'PATCH');
|
|
|
|
|
expect(el().querySelector('.status')?.textContent).toContain('No hay cambios');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('envía solo lo que cambió, con If-Match, y recarga el documento', async () => {
|
|
|
|
|
page().setHeader('Comments', 'nuevo comentario');
|
|
|
|
|
await tick();
|
|
|
|
|
expect(el().querySelector('.status')?.textContent).toContain('cambios sin guardar');
|
|
|
|
|
(el().querySelector('.actions .btn.main') as HTMLElement).click();
|
|
|
|
|
await tick();
|
|
|
|
|
const req = ctrl.expectOne((r) => r.method === 'PATCH');
|
|
|
|
|
expect(req.request.url).toBe('/api/sl/Orders/41');
|
|
|
|
|
expect(req.request.body).toEqual({ Comments: 'nuevo comentario' });
|
|
|
|
|
expect(req.request.headers.get('If-Match')).toBe('W/"77"');
|
|
|
|
|
expect(req.request.headers.has('B1S-ReplaceCollectionsOnPatch')).toBe(false);
|
|
|
|
|
req.flush(null, { status: 204, statusText: 'No Content' });
|
|
|
|
|
await tick();
|
|
|
|
|
ctrl.expectOne('/api/sl/Orders/41').flush(order({ Comments: 'nuevo comentario', 'odata.etag': 'W/"78"' }));
|
|
|
|
|
await tick();
|
|
|
|
|
expect(el().querySelector('.status')?.textContent).toContain('Documento actualizado');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('si cambian las líneas las envía todas con ReplaceCollectionsOnPatch', async () => {
|
|
|
|
|
const lines = page().lines();
|
|
|
|
|
page().setLines([{ ...lines[0], Quantity: 5, LineTotal: 500 }]);
|
|
|
|
|
await tick();
|
|
|
|
|
(el().querySelector('.actions .btn.main') as HTMLElement).click();
|
|
|
|
|
await tick();
|
|
|
|
|
const req = ctrl.expectOne((r) => r.method === 'PATCH');
|
|
|
|
|
expect(req.request.headers.get('B1S-ReplaceCollectionsOnPatch')).toBe('true');
|
|
|
|
|
expect(req.request.body.DocumentLines[0]).toMatchObject({ ItemCode: 'A1', Quantity: 5 });
|
|
|
|
|
expect(req.request.body.DocumentLines[0].LineTotal).toBeUndefined();
|
|
|
|
|
req.flush(null, { status: 204, statusText: 'No Content' });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('un 412 avisa del conflicto, no guarda y ofrece Recargar', async () => {
|
|
|
|
|
page().setHeader('Comments', 'x');
|
|
|
|
|
await tick();
|
|
|
|
|
(el().querySelector('.actions .btn.main') as HTMLElement).click();
|
|
|
|
|
await tick();
|
|
|
|
|
ctrl.expectOne((r) => r.method === 'PATCH').flush({ message: 'etag' }, { status: 412, statusText: 'Precondition Failed' });
|
|
|
|
|
await tick();
|
|
|
|
|
expect(el().querySelector('.status')?.textContent).toContain('Otro usuario modificó');
|
|
|
|
|
expect(buttonText()).toContain('Recargar');
|
|
|
|
|
(el().querySelectorAll('.actions .btn')[1] as HTMLElement).click();
|
|
|
|
|
await tick();
|
|
|
|
|
ctrl.expectOne('/api/sl/Orders/41').flush(order({ Comments: 'del otro usuario' }));
|
|
|
|
|
await tick();
|
|
|
|
|
expect(page().header().Comments).toBe('del otro usuario');
|
|
|
|
|
expect(buttonText()).not.toContain('Recargar');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('la navegación pide el documento vecino y abre su DocEntry', async () => {
|
|
|
|
|
const done = page().go('next');
|
|
|
|
|
await tick();
|
|
|
|
|
const req = ctrl.expectOne((r) => r.url === '/api/sl/Orders' && r.params.get('$filter') === 'DocEntry gt 41');
|
|
|
|
|
expect(req.request.params.get('$orderby')).toBe('DocEntry asc');
|
|
|
|
|
expect(req.request.params.get('$top')).toBe('1');
|
|
|
|
|
req.flush({ value: [{ DocEntry: 42 }] });
|
|
|
|
|
await done;
|
|
|
|
|
expect(navigate).toHaveBeenCalledWith(['..', 42], expect.anything());
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('al llegar al extremo avisa y no navega', async () => {
|
|
|
|
|
const done = page().go('next');
|
|
|
|
|
await tick();
|
|
|
|
|
ctrl.expectOne((r) => r.url === '/api/sl/Orders').flush({ value: [] });
|
|
|
|
|
await done;
|
|
|
|
|
await tick();
|
|
|
|
|
expect(navigate).not.toHaveBeenCalled();
|
|
|
|
|
expect(el().querySelector('.status')?.textContent).toContain('No hay más documentos');
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('documento cerrado', () => {
|
|
|
|
|
beforeEach(() => open('41', order({ DocumentStatus: 'bost_Close' })));
|
|
|
|
|
|
|
|
|
|
it('queda en solo lectura: sin Actualizar y con los campos bloqueados', () => {
|
|
|
|
|
expect(buttonText()).toEqual(['Cerrar']);
|
|
|
|
|
expect(el().querySelector('.status')?.textContent).toContain('solo lectura');
|
|
|
|
|
const comments = el().querySelector('textarea') as HTMLTextAreaElement;
|
|
|
|
|
expect(comments.readOnly).toBe(true);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('documento nuevo', () => {
|
|
|
|
|
beforeEach(() => open(null));
|
|
|
|
|
|
|
|
|
|
it('arranca vacío con fecha de hoy y botón Crear', () => {
|
|
|
|
|
expect(el().querySelector('.badge.tone-info')?.textContent?.trim()).toBe('Nueva');
|
|
|
|
|
expect(buttonText()).toContain('Crear');
|
|
|
|
|
expect(page().header().DocDate).toMatch(/^\d{4}-\d{2}-\d{2}$/);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('no envía nada si falta el cliente o las líneas', async () => {
|
|
|
|
|
(el().querySelector('.actions .btn.main') as HTMLElement).click();
|
|
|
|
|
await tick();
|
2026-10-16 11:07:38 -05:00
|
|
|
ctrl.expectNone((r) => r.method === 'POST' && !r.url.includes('SeriesService'));
|
2026-10-13 22:00:54 -05:00
|
|
|
expect(el().querySelector('.status')?.textContent).toContain('Indique el cliente');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('crea el documento sin campos vacíos y abre el creado', async () => {
|
|
|
|
|
page().setHeader('CardCode', 'C1');
|
|
|
|
|
page().setLines([{ ItemCode: 'A1', Quantity: 1, Price: 50, DiscountPercent: 0, TaxCode: '', ItemDescription: '', LineTotal: 50 }]);
|
|
|
|
|
await tick();
|
|
|
|
|
(el().querySelector('.actions .btn.main') as HTMLElement).click();
|
|
|
|
|
await tick();
|
2026-10-16 11:07:38 -05:00
|
|
|
const req = ctrl.expectOne((r) => r.method === 'POST' && !r.url.includes('SeriesService'));
|
2026-10-13 22:00:54 -05:00
|
|
|
expect(req.request.url).toBe('/api/sl/Orders');
|
|
|
|
|
expect(req.request.body).toEqual({
|
2026-10-19 12:19:18 -05:00
|
|
|
DocType: 'dDocument_Items',
|
2026-10-13 22:00:54 -05:00
|
|
|
CardCode: 'C1',
|
|
|
|
|
DocDate: page().header().DocDate,
|
|
|
|
|
DocDueDate: page().header().DocDueDate,
|
|
|
|
|
DiscountPercent: 0,
|
|
|
|
|
DocumentLines: [{ ItemCode: 'A1', Quantity: 1, Price: 50, DiscountPercent: 0 }],
|
|
|
|
|
});
|
|
|
|
|
req.flush({ DocEntry: 99, DocNum: 1099 });
|
|
|
|
|
await tick();
|
|
|
|
|
expect(navigate).toHaveBeenCalledWith(['..', 99], expect.objectContaining({ state: { created: '1099' } }));
|
|
|
|
|
});
|
|
|
|
|
|
2026-10-19 12:28:56 -05:00
|
|
|
it('al elegir el socio de un documento nuevo sin líneas se agrega una línea, y no se repite', async () => {
|
|
|
|
|
expect(page().lines()).toEqual([]);
|
|
|
|
|
page().onPartner({ CardCode: 'C1', CardName: 'ACME' });
|
|
|
|
|
await tick();
|
|
|
|
|
expect(page().lines()).toHaveLength(1);
|
|
|
|
|
page().onPartner({ CardCode: 'C2', CardName: 'OTRO' });
|
|
|
|
|
expect(page().lines()).toHaveLength(1);
|
|
|
|
|
});
|
|
|
|
|
|
2026-10-19 12:19:18 -05:00
|
|
|
describe('tipo Servicio', () => {
|
|
|
|
|
const headers = () => [...el().querySelectorAll('.lines-head th, table.grid thead th')].map((t) => t.textContent?.trim());
|
|
|
|
|
|
|
|
|
|
it('un documento de servicio cambia las columnas: cuenta contable, descripción, impuesto y total, sin artículo ni cantidad', async () => {
|
|
|
|
|
page().setHeader('DocType', 'dDocument_Service');
|
|
|
|
|
await tick();
|
|
|
|
|
const titles = headers();
|
|
|
|
|
expect(titles).toEqual(expect.arrayContaining(['Cuenta contable', 'Descripción', 'Impuesto', 'Total']));
|
|
|
|
|
expect(titles).not.toContain('Nº de artículo');
|
|
|
|
|
expect(titles).not.toContain('Cantidad');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('crea el documento con DocType de servicio y líneas de cuenta y total', async () => {
|
|
|
|
|
page().setHeader('DocType', 'dDocument_Service');
|
|
|
|
|
page().setHeader('CardCode', 'P1');
|
|
|
|
|
page().setLines([{ AccountCode: '_SYS1', ItemDescription: 'Alquiler', TaxCode: 'IGV', LineTotal: 100, ItemCode: '' }]);
|
|
|
|
|
await tick();
|
|
|
|
|
(el().querySelector('.actions .btn.main') as HTMLElement).click();
|
|
|
|
|
await tick();
|
|
|
|
|
const req = ctrl.expectOne((r) => r.method === 'POST' && !r.url.includes('SeriesService'));
|
|
|
|
|
expect(req.request.body).toMatchObject({ DocType: 'dDocument_Service', CardCode: 'P1' });
|
|
|
|
|
expect((req.request.body as { DocumentLines: unknown[] }).DocumentLines).toEqual([{ AccountCode: '_SYS1', ItemDescription: 'Alquiler', TaxCode: 'IGV', LineTotal: 100 }]);
|
|
|
|
|
req.flush({ DocEntry: 7, DocNum: 1007 });
|
|
|
|
|
await tick();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('exige cuenta contable y un total mayor que cero', async () => {
|
|
|
|
|
page().setHeader('DocType', 'dDocument_Service');
|
|
|
|
|
page().setHeader('CardCode', 'P1');
|
|
|
|
|
page().setLines([{ ItemDescription: 'Sin cuenta', LineTotal: 10 }]);
|
|
|
|
|
await tick();
|
|
|
|
|
(el().querySelector('.actions .btn.main') as HTMLElement).click();
|
|
|
|
|
await tick();
|
|
|
|
|
ctrl.expectNone((r) => r.method === 'POST' && !r.url.includes('SeriesService'));
|
|
|
|
|
expect(el().querySelector('.status')!.textContent).toContain('indique la cuenta contable');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('cambiar el tipo con líneas escritas pide permiso y, si se acepta, las descarta', async () => {
|
|
|
|
|
page().setLines([{ ItemCode: 'A1', Quantity: 1, Price: 50, LineTotal: 50 }]);
|
|
|
|
|
await tick();
|
|
|
|
|
page().setHeader('DocType', 'dDocument_Service');
|
|
|
|
|
await tick();
|
|
|
|
|
expect(el().querySelector('.ask')!.textContent).toContain('descarta la línea escrita');
|
|
|
|
|
expect(page().header().DocType).toBe('dDocument_Items'); // todavía no cambió
|
|
|
|
|
(el().querySelector('.ask .btn.main') as HTMLElement).click();
|
|
|
|
|
await tick();
|
|
|
|
|
expect(page().header().DocType).toBe('dDocument_Service');
|
|
|
|
|
expect(page().lines()).toEqual([]);
|
|
|
|
|
expect(el().querySelector('.ask')).toBeNull();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2026-10-13 22:00:54 -05:00
|
|
|
it('muestra el error de SAP si rechaza el documento', async () => {
|
|
|
|
|
page().setHeader('CardCode', 'C1');
|
|
|
|
|
page().setLines([{ ItemCode: 'A1', Quantity: 1, Price: 50, LineTotal: 50 }]);
|
|
|
|
|
await tick();
|
|
|
|
|
(el().querySelector('.actions .btn.main') as HTMLElement).click();
|
|
|
|
|
await tick();
|
2026-10-16 11:07:38 -05:00
|
|
|
ctrl.expectOne((r) => r.method === 'POST' && !r.url.includes('SeriesService')).flush({ message: 'Código de cliente inválido' }, { status: 400, statusText: 'Bad Request' });
|
2026-10-13 22:00:54 -05:00
|
|
|
await tick();
|
|
|
|
|
expect(el().querySelector('.status')?.textContent).toContain('Código de cliente inválido');
|
|
|
|
|
expect(navigate).not.toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
});
|
2026-10-14 11:59:07 -05:00
|
|
|
|
|
|
|
|
describe('copiar a', () => {
|
|
|
|
|
const items = () => [...el().querySelectorAll<HTMLButtonElement>('sbo-menu-button')].map((m) => m.textContent?.trim());
|
|
|
|
|
|
2026-10-15 17:32:32 -05:00
|
|
|
it('un documento abierto y guardado ofrece Copiar a con entrega, factura, boleta y sus reservas', async () => {
|
2026-10-14 11:59:07 -05:00
|
|
|
await open('41', order());
|
|
|
|
|
expect(items().join(' ')).toContain('Copiar a');
|
|
|
|
|
const ids = page().copyToItems().map((i: { id: string; disabled: boolean }) => [i.id, i.disabled]);
|
2026-10-15 17:32:32 -05:00
|
|
|
expect(ids.map((i: [string, boolean]) => i[0])).toEqual([
|
|
|
|
|
'/ventas/entrega', '/ventas/facturas', '/ventas/boletas', '/ventas/facturas-reserva', '/ventas/boletas-reserva',
|
|
|
|
|
]);
|
|
|
|
|
expect(ids.every((i: [string, boolean]) => i[1] === false)).toBe(true);
|
2026-10-14 11:59:07 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('abre el documento nuevo de destino llevando el origen', async () => {
|
|
|
|
|
await open('41', order());
|
|
|
|
|
const nav = vi.spyOn(TestBed.inject(Router), 'navigateByUrl').mockResolvedValue(true);
|
|
|
|
|
page().copyTo('/ventas/entrega');
|
|
|
|
|
expect(nav).toHaveBeenCalledWith('/ventas/entrega/nuevo', { state: { copyFrom: { path: '/ventas/ordenes', docEntry: 41 } } });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('con cambios sin guardar o cerrado no se puede copiar', async () => {
|
|
|
|
|
await open('41', order());
|
|
|
|
|
page().setHeader('Comments', 'x');
|
|
|
|
|
await tick();
|
|
|
|
|
expect(page().copyToItems().every((i: { disabled: boolean }) => i.disabled)).toBe(true);
|
|
|
|
|
TestBed.resetTestingModule();
|
|
|
|
|
await open('41', order({ DocumentStatus: 'bost_Close' }));
|
|
|
|
|
expect(page().copyToItems().every((i: { disabled: boolean }) => i.disabled)).toBe(true);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('copiar de / a: documento nuevo con origen', () => {
|
|
|
|
|
const source = {
|
|
|
|
|
DocEntry: 7, DocNum: 1007, CardCode: 'C1', CardName: 'ACME', NumAtCard: 'REF', DiscountPercent: 0,
|
|
|
|
|
DocumentLines: [
|
|
|
|
|
{ LineNum: 0, ItemCode: 'A1', ItemDescription: 'Pieza', Quantity: 4, RemainingOpenQuantity: 3, WarehouseCode: '01', Price: 100, DiscountPercent: 0, TaxCode: 'IVA', LineStatus: 'bost_Open' },
|
|
|
|
|
{ LineNum: 1, ItemCode: 'B2', Quantity: 1, RemainingOpenQuantity: 0, LineStatus: 'bost_Close' },
|
|
|
|
|
],
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
it('al llegar con un origen lo carga: socio, líneas abiertas y referencia al origen', async () => {
|
|
|
|
|
await open(null, undefined, { path: '/ventas/ordenes', docEntry: 7 }, () => undefined);
|
|
|
|
|
ctrl.expectOne('/api/sl/Orders/7').flush(source);
|
|
|
|
|
await tick();
|
|
|
|
|
expect(page().header().CardCode).toBe('C1');
|
|
|
|
|
expect(page().partnerName()).toBe('ACME');
|
|
|
|
|
expect(page().lines()).toHaveLength(1);
|
|
|
|
|
expect(page().lines()[0]).toMatchObject({ ItemCode: 'A1', Quantity: 3, BaseType: 17, BaseEntry: 7, BaseLine: 0 });
|
|
|
|
|
expect(el().querySelector('.status')?.textContent).toContain('Copiado de órdenes de venta 1007');
|
|
|
|
|
expect(el().querySelector('.status')?.textContent).toContain('1 línea(s) cerradas');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('al crear envía las referencias al origen en las líneas', async () => {
|
|
|
|
|
await open(null, undefined, { path: '/ventas/ordenes', docEntry: 7 });
|
|
|
|
|
ctrl.expectOne('/api/sl/Orders/7').flush(source);
|
|
|
|
|
await tick();
|
|
|
|
|
(el().querySelector('.actions .btn.main') as HTMLElement).click();
|
|
|
|
|
await tick();
|
2026-10-16 11:07:38 -05:00
|
|
|
const req = ctrl.expectOne((r) => r.method === 'POST' && !r.url.includes('SeriesService'));
|
2026-10-14 11:59:07 -05:00
|
|
|
expect(req.request.body.DocumentLines).toEqual([
|
|
|
|
|
{ ItemCode: 'A1', ItemDescription: 'Pieza', Quantity: 3, WarehouseCode: '01', Price: 100, DiscountPercent: 0, TaxCode: 'IVA', BaseType: 17, BaseEntry: 7, BaseLine: 0 },
|
|
|
|
|
]);
|
|
|
|
|
req.flush({ DocEntry: 99, DocNum: 1099 });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('un origen sin líneas abiertas no copia nada y avisa', async () => {
|
|
|
|
|
await open(null, undefined, { path: '/ventas/ordenes', docEntry: 7 });
|
|
|
|
|
ctrl.expectOne('/api/sl/Orders/7').flush({ ...source, DocumentLines: [source.DocumentLines[1]] });
|
|
|
|
|
await tick();
|
|
|
|
|
expect(page().lines()).toHaveLength(0);
|
|
|
|
|
expect(el().querySelector('.status')?.textContent).toContain('no tiene líneas abiertas');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('Copiar de: no mezcla socios distintos', async () => {
|
|
|
|
|
await open(null);
|
|
|
|
|
page().setHeader('CardCode', 'C9');
|
|
|
|
|
await tick();
|
|
|
|
|
page().copyListFor.set('/ventas/ordenes');
|
|
|
|
|
const done = page().copyPicked([{ DocEntry: 7 }]);
|
|
|
|
|
await Promise.resolve();
|
|
|
|
|
ctrl.expectOne('/api/sl/Orders/7').flush(source);
|
|
|
|
|
await done;
|
|
|
|
|
await tick();
|
|
|
|
|
expect(page().lines()).toHaveLength(0);
|
|
|
|
|
expect(el().querySelector('.status')?.textContent).toContain('es de C1');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('Copiar de: ofrece solo los orígenes del flujo y lista los abiertos del socio', async () => {
|
|
|
|
|
await open(null);
|
|
|
|
|
expect(page().copyFromItems().map((i: { id: string }) => i.id)).toEqual(['/ventas/ofertas']);
|
|
|
|
|
expect(page().copyListFilter()).toBe("DocumentStatus eq 'bost_Open' and Cancelled eq 'tNO'");
|
|
|
|
|
page().setHeader('CardCode', "C'1");
|
|
|
|
|
expect(page().copyListFilter()).toContain("CardCode eq 'C''1'");
|
|
|
|
|
});
|
|
|
|
|
});
|
2026-10-13 22:00:54 -05:00
|
|
|
});
|