Files
devolucion_proveedores/libs/shared/src/domain/fifo.spec.ts
T

82 lines
3.8 KiB
TypeScript
Raw Normal View History

import { describe, expect, it } from 'vitest';
import { EntradaAbierta, LineaPlanificacion } from '../models/planificacion.js';
import { asignarFifo, consolidarDocumentos, extraerLineasAplicables, puedeCambiarSeleccion } from './fifo.js';
function entrada(docEntry: number, docDate: string, openQty: number, cardCode = 'P1'): EntradaAbierta {
return { docEntry, lineNum: 0, docNum: docEntry + 1000, docDate, cardCode, cardName: `Prov ${cardCode}`, openQty };
}
describe('asignarFifo', () => {
it('reparte la cantidad desde la Entrada más antigua', () => {
const entradas = new Map([['A001', [entrada(2, '2026-02-01', 10), entrada(1, '2026-01-01', 4)]]]);
const { lineas, advertencias } = asignarFifo([{ itemCode: 'a001', itemName: 'Art', quantity: 6, onHand: 20 }], entradas);
expect(advertencias).toEqual([]);
expect(lineas.map((l) => [l.tipoFila, l.baseEntry, l.allocatedQty])).toEqual([
['F', null, null],
['S', 1, 4],
['S', 2, 2],
]);
expect(lineas[1]).toMatchObject({ itemCode: 'a001', applied: true, baseType: 20, baseRef: 1001 });
});
it('omite artículos sin stock suficiente', () => {
const { lineas, advertencias } = asignarFifo([{ itemCode: 'A001', itemName: 'Art', quantity: 6, onHand: 5 }], new Map());
expect(lineas).toEqual([]);
expect(advertencias[0]).toContain('sin stock');
});
it('advierte cuando las Entradas no alcanzan', () => {
const entradas = new Map([['A001', [entrada(1, '2026-01-01', 2)]]]);
const { lineas, advertencias } = asignarFifo([{ itemCode: 'A001', itemName: 'Art', quantity: 5, onHand: 5 }], entradas);
expect(lineas).toHaveLength(2);
expect(advertencias[0]).toContain('faltan 3');
});
});
describe('extraerLineasAplicables / puedeCambiarSeleccion', () => {
const base = asignarFifo(
[{ itemCode: 'A001', itemName: 'Art', quantity: 5, onHand: 5 }],
new Map([['A001', [entrada(1, '2026-01-01', 2), entrada(2, '2026-01-02', 2), entrada(3, '2026-01-03', 2)]]]),
).lineas;
it('devuelve solo Hijos marcados sin documento destino', () => {
const lineas: LineaPlanificacion[] = base.map((l) => ({ ...l }));
lineas[2].applied = false;
lineas[3].targetEntry = 99;
expect(extraerLineasAplicables(lineas).map((l) => l.baseEntry)).toEqual([1]);
});
it('hereda el ItemCode del Padre si el Hijo no lo trae', () => {
const lineas = base.map((l) => (l.tipoFila === 'S' ? { ...l, itemCode: '' } : l));
expect(extraerLineasAplicables(lineas)[0].itemCode).toBe('A001');
});
it('bloquea la selección de líneas con documento destino', () => {
expect(puedeCambiarSeleccion(base[1])).toBe(true);
expect(puedeCambiarSeleccion({ ...base[1], targetEntry: 10 })).toBe(false);
expect(puedeCambiarSeleccion(base[0])).toBe(false);
});
});
describe('consolidarDocumentos', () => {
it('agrupa por proveedor y corta en lotes', () => {
const entradas = new Map([
['A001', [entrada(1, '2026-01-01', 1, 'P1'), entrada(2, '2026-01-02', 1, 'P2'), entrada(3, '2026-01-03', 1, 'P1'), entrada(4, '2026-01-04', 1, 'P1')]],
]);
const { lineas } = asignarFifo([{ itemCode: 'A001', itemName: 'Art', quantity: 4, onHand: 4 }], entradas);
const docs = consolidarDocumentos(extraerLineasAplicables(lineas), 2);
expect(docs.map((d) => [d.numero, d.cardCode, d.lineas.map((l) => l.baseEntry)])).toEqual([
[1, 'P1', [1, 3]],
[2, 'P1', [4]],
[3, 'P2', [2]],
]);
});
it('usa 11 líneas si el máximo no es válido', () => {
const entradas = new Map([['A001', Array.from({ length: 12 }, (_, i) => entrada(i + 1, '2026-01-01', 1))]]);
const { lineas } = asignarFifo([{ itemCode: 'A001', itemName: 'Art', quantity: 12, onHand: 12 }], entradas);
expect(consolidarDocumentos(extraerLineasAplicables(lineas), 0).map((d) => d.lineas.length)).toEqual([11, 1]);
});
});