Files
basic-erp/apps/web/src/app/features/documents/line-details.spec.ts
T

96 lines
6.4 KiB
TypeScript
Raw Normal View History

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');
});
});
});