import { describe, expect, it } from 'vitest'; import { createPayload } from './document-model'; import { DOCUMENT_SCREENS } from './document-screens'; import { BATCH_DETAIL, SERIAL_DETAIL, detailHint, detailRowsPayload, validateLineDetails } from './line-details'; describe('lotes y series de una línea', () => { it('el lote aplica si el artículo se gestiona por lote o la línea ya trae lotes; no si no', () => { expect(BATCH_DETAIL.applies({ _manageBatches: true })).toBe(true); expect(BATCH_DETAIL.applies({ BatchNumbers: [{ BatchNumber: 'L1', Quantity: 2 }] })).toBe(true); expect(BATCH_DETAIL.applies({ _manageBatches: false, BatchNumbers: [] })).toBe(false); expect(BATCH_DETAIL.applies({})).toBe(false); expect(SERIAL_DETAIL.applies({ _manageSerials: true })).toBe(true); expect(SERIAL_DETAIL.applies({ _manageBatches: true })).toBe(false); }); it('el botón resume lo indicado', () => { expect(BATCH_DETAIL.summary({ _manageBatches: true })).toBe('Indicar lotes'); expect(BATCH_DETAIL.summary({ BatchNumbers: [{ BatchNumber: 'A', Quantity: 3 }, { BatchNumber: 'B', Quantity: 2 }] })).toBe('2 lotes · 5'); expect(BATCH_DETAIL.summary({ BatchNumbers: [{ BatchNumber: 'A', Quantity: 1 }] })).toBe('1 lote · 1'); expect(SERIAL_DETAIL.summary({ _manageSerials: true })).toBe('Indicar series'); expect(SERIAL_DETAIL.summary({ SerialNumbers: [{ InternalSerialNumber: 'S1' }, { InternalSerialNumber: 'S2' }] })).toBe('2 series'); }); it('el aviso compara las filas en edición con la cantidad de la línea', () => { expect(detailHint(BATCH_DETAIL, { Quantity: 10 }, [{ Quantity: 6 }, { Quantity: 4 }])).toEqual({ text: 'Cantidad de la línea: 10 · los lotes suman: 10', bad: false }); expect(detailHint(BATCH_DETAIL, { Quantity: 10 }, [{ Quantity: 6 }]).bad).toBe(true); expect(detailHint(SERIAL_DETAIL, { Quantity: 2 }, [{ InternalSerialNumber: 'S1' }, { InternalSerialNumber: '' }])).toEqual({ text: 'Series: 1 de 2', bad: true }); }); it('a SAP solo van los campos propios, sin vacíos ni filas sin lote, con fechas AAAA-MM-DD y cada serie con cantidad 1', () => { expect( detailRowsPayload('BatchNumbers', [ { BatchNumber: 'L1', Quantity: 4, ExpiryDate: '2027-01-31T00:00:00Z', Notes: 'x', SystemSerialNumber: 7, ManufacturingDate: '' }, { BatchNumber: ' ', Quantity: 9 }, ]), ).toEqual([{ BatchNumber: 'L1', Quantity: 4, ExpiryDate: '2027-01-31' }]); expect(detailRowsPayload('SerialNumbers', [{ InternalSerialNumber: 'S1', ManufacturerSerialNumber: 'M1' }])).toEqual([ { InternalSerialNumber: 'S1', ManufacturerSerialNumber: 'M1', Quantity: 1 }, ]); expect(detailRowsPayload('Otro', [{ A: 1 }])).toEqual([]); expect(detailRowsPayload('BatchNumbers', undefined)).toEqual([]); }); describe('validación', () => { it('un artículo por lote necesita lotes que sumen la cantidad', () => { expect(validateLineDetails([{ ItemCode: 'SU01', Quantity: 5, _manageBatches: true }])).toEqual(['Línea 1: indique los lotes de SU01.']); expect(validateLineDetails([{ ItemCode: 'SU01', Quantity: 5, _manageBatches: true, BatchNumbers: [{ BatchNumber: 'L1', Quantity: 3 }] }])).toEqual([ 'Línea 1: los lotes suman 3 y la cantidad es 5.', ]); expect(validateLineDetails([{ ItemCode: 'SU01', Quantity: 5, _manageBatches: true, BatchNumbers: [{ BatchNumber: 'L1', Quantity: 3 }, { BatchNumber: 'L2', Quantity: 2 }] }])).toEqual([]); }); it('un artículo por serie necesita una serie por unidad', () => { expect(validateLineDetails([{ ItemCode: 'A6', Quantity: 2, _manageSerials: true, SerialNumbers: [{ InternalSerialNumber: 'S1' }] }])).toEqual([ 'Línea 2: indique 2 número(s) de serie de A6 (hay 1).'.replace('Línea 2', 'Línea 1'), ]); expect(validateLineDetails([{ ItemCode: 'A6', Quantity: 2, _manageSerials: true, SerialNumbers: [{ InternalSerialNumber: 'S1' }, { InternalSerialNumber: 'S2' }] }])).toEqual([]); }); it('las líneas sin lotes ni series, y las vacías, no se validan; se numera solo lo real', () => { expect(validateLineDetails([{ ItemCode: '', Quantity: 1 }, { ItemCode: 'X', Quantity: 1 }])).toEqual([]); expect(validateLineDetails([{ ItemCode: '', Quantity: 1 }, { ItemCode: 'SU01', Quantity: 1, _manageBatches: true }])).toEqual(['Línea 1: indique los lotes de SU01.']); }); }); describe('qué documentos los llevan', () => { const lines = (p: string) => DOCUMENT_SCREENS.find((s) => s.path === p)!.layout; it('entradas, salidas y transferencias de stock sí; solicitudes de traslado, de compra y ventas no', () => { for (const p of ['/inventario/entradas', '/inventario/salidas', '/inventario/transferencias']) { expect(lines(p).details?.map((d) => d.key), p).toEqual(['BatchNumbers', 'SerialNumbers']); expect(lines(p).lines.fields, p).toEqual(expect.arrayContaining(['BatchNumbers', 'SerialNumbers'])); } for (const p of ['/inventario/solicitudes-traslado', '/compras/solicitudes', '/ventas/ordenes']) { expect(lines(p).details, p).toBeUndefined(); expect(lines(p).lines.fields, p).not.toContain('BatchNumbers'); } }); it('los lotes viajan dentro de la línea, en la colección que espera SAP', () => { const entry = lines('/inventario/entradas').lines; const body = createPayload({}, [{ ItemCode: 'SU01', Quantity: 5, WarehouseCode: '01', _manageBatches: true, BatchNumbers: [{ BatchNumber: 'L1', Quantity: 5, ExpiryDate: '2027-01-01' }] }], entry); expect(body['DocumentLines']).toEqual([ { ItemCode: 'SU01', Quantity: 5, WarehouseCode: '01', BatchNumbers: [{ BatchNumber: 'L1', Quantity: 5, ExpiryDate: '2027-01-01' }] }, ]); const transfer = createPayload({}, [{ ItemCode: 'A6', Quantity: 1, FromWarehouseCode: '01', WarehouseCode: '02', SerialNumbers: [{ InternalSerialNumber: 'S1' }] }], lines('/inventario/transferencias').lines); expect(transfer['StockTransferLines']).toEqual([ { ItemCode: 'A6', Quantity: 1, FromWarehouseCode: '01', WarehouseCode: '02', SerialNumbers: [{ InternalSerialNumber: 'S1', Quantity: 1 }] }, ]); }); it('la marca interna de «gestiona lotes» nunca se envía', () => { const body = createPayload({}, [{ ItemCode: 'A', Quantity: 1, WarehouseCode: '01', _manageBatches: true, _manageSerials: false }], lines('/inventario/entradas').lines); expect(JSON.stringify(body)).not.toContain('_manage'); }); }); });