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; 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 = { 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(); }); });