Files
basic-erp/apps/web/src/app/features/client/lotes/lote-release-confirm.page.spec.ts
T

123 lines
6.1 KiB
TypeScript
Raw Normal View History

import { provideHttpClient } from '@angular/common/http';
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
import { type ComponentFixture, TestBed } from '@angular/core/testing';
import { provideRouter } from '@angular/router';
import { beforeEach, describe, expect, it } from 'vitest';
import { LoteReleaseConfirmPage } from './lote-release-confirm.page';
const PENDING = [{ DocEntry: 480, DocNum: 4123, DocDate: '2026-10-19T00:00:00Z', U_SIS_MOTEST: 'Aprobado por calidad', U_SIS_COMEST: '', U_SIS_ACCTOM: '' }];
const REQUEST = {
'odata.etag': 'W/"5"',
DocEntry: 480,
DocNum: 4123,
StockTransferLines: [
{ LineNum: 0, ItemCode: 'A1', Quantity: 3, WarehouseCode: '201', FromWarehouseCode: '302', BatchNumbers: [{ BatchNumber: 'L1', Quantity: 3 }] },
{ LineNum: 1, ItemCode: 'B2', Quantity: 2, WarehouseCode: '201', FromWarehouseCode: '302', BatchNumbers: [{ BatchNumber: 'L2', Quantity: 2 }] },
],
};
describe('LoteReleaseConfirmPage', () => {
let fixture: ComponentFixture<LoteReleaseConfirmPage>;
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();
};
const isList = (r: { method: string; url: string }) => r.method === 'GET' && r.url === '/api/sl/InventoryTransferRequests';
async function open() {
TestBed.resetTestingModule();
await TestBed.configureTestingModule({ imports: [LoteReleaseConfirmPage], providers: [provideRouter([]), provideHttpClient(), provideHttpClientTesting()] }).compileComponents();
ctrl = TestBed.inject(HttpTestingController);
fixture = TestBed.createComponent(LoteReleaseConfirmPage);
await tick();
ctrl.expectOne(isList).flush({ value: PENDING });
await tick();
}
/** Elige la solicitud: se pide completa y, por cada línea, de qué almacenes vino su lote. */
async function select(origins: Record<string, { WarehouseCode: string; Quantity: string }[]> = { A1: [{ WarehouseCode: '207', Quantity: '2.000000' }, { WarehouseCode: '208', Quantity: '1.000000' }], B2: [] }) {
(el().querySelector('.grid tbody tr') as HTMLElement).click();
await tick();
ctrl.expectOne('/api/sl/InventoryTransferRequests/480').flush(REQUEST);
await tick();
for (const item of ['A1', 'B2']) {
const req = ctrl.expectOne((r) => r.url === '/api/sapdb/queries/lot-sources' && r.body.params.item === item);
expect(req.request.body.params).toMatchObject({ warehouse: '302' });
req.flush({ rows: origins[item] ?? [], truncated: false });
}
await tick();
}
beforeEach(open);
it('lista las solicitudes de liberación abiertas (motivo 108), las más recientes primero', async () => {
expect(el().querySelector('.grid tbody tr')?.textContent).toContain('4123');
expect(el().querySelector('.grid tbody tr')?.textContent).toContain('19/10/2026');
await tick();
});
it('al elegir una solicitud propone el reparto por los almacenes de los que vino el lote', async () => {
await select();
const d = page().drafts();
expect(d[0].allocations).toEqual([{ WarehouseCode: '207', Quantity: 2 }, { WarehouseCode: '208', Quantity: 1 }]);
expect(el().textContent).toContain('lote L1');
// la línea de la que no se encontró origen se avisa para agregarla a mano
expect(d[1].allocations).toEqual([]);
expect(el().textContent).toContain('no se encontró de qué almacén vino');
});
it('no deja confirmar si el reparto no suma la cantidad de la línea', async () => {
await select();
expect(page().canConfirm()).toBe(true); // la línea sin filas se quita a propósito
page().setAllocations(0, [{ WarehouseCode: '207', Quantity: 1 }]);
await tick();
expect(page().canConfirm()).toBe(false);
expect(el().textContent).toContain('suman 1 y la línea es de 3');
page().addAllocation(0);
await tick();
expect(page().canConfirm()).toBe(false); // la fila nueva está incompleta
expect(el().textContent).toContain('Complete el almacén y la cantidad de cada fila');
});
it('confirma reemplazando las líneas: la primera de cada una con su LineNum, las demás sin él', async () => {
await select();
page().setAllocations(1, [{ WarehouseCode: '201', Quantity: 2 }]);
await tick();
page().confirm();
await tick();
const req = ctrl.expectOne((r) => r.method === 'PATCH');
expect(req.request.url).toBe('/api/sl/InventoryTransferRequests/480');
expect(req.request.headers.get('If-Match')).toBe('W/"5"');
expect(req.request.headers.get('B1S-ReplaceCollectionsOnPatch')).toBe('true');
expect(req.request.body).toEqual({
StockTransferLines: [
{ LineNum: 0, ItemCode: 'A1', Quantity: 2, WarehouseCode: '207', FromWarehouseCode: '302', BatchNumbers: [{ BatchNumber: 'L1', Quantity: 2 }] },
{ ItemCode: 'A1', Quantity: 1, WarehouseCode: '208', FromWarehouseCode: '302', BatchNumbers: [{ BatchNumber: 'L1', Quantity: 1 }] },
{ LineNum: 1, ItemCode: 'B2', Quantity: 2, WarehouseCode: '201', FromWarehouseCode: '302', BatchNumbers: [{ BatchNumber: 'L2', Quantity: 2 }] },
],
});
req.flush(null);
await tick();
expect(el().querySelector('.status')?.textContent).toContain('Solicitud 4123 actualizada');
// la solicitud confirmada sale de la lista pendiente
ctrl.expectOne(isList).flush({ value: [] });
await tick();
expect(el().textContent).toContain('No hay solicitudes de liberación pendientes');
});
it('un error de SAP al confirmar se muestra y deja el reparto para corregirlo', async () => {
await select();
page().setAllocations(1, [{ WarehouseCode: '201', Quantity: 2 }]);
page().confirm();
await tick();
ctrl.expectOne((r) => r.method === 'PATCH').flush({ message: 'El almacén está bloqueado' }, { status: 400, statusText: 'Bad Request' });
await tick();
expect(el().querySelector('.status')?.textContent).toContain('El almacén está bloqueado');
expect(page().drafts()).not.toBeNull();
});
});