You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
65 lines
2.4 KiB
TypeScript
65 lines
2.4 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import { isExpired, pickFefoBatch, sortFefo, type BatchStock } from '@sims/domain'
|
|
|
|
/**
|
|
* S5 FEFO — the single batch-pick rule shared by the POS picker and the expiry
|
|
* dashboard. Dates are ISO strings so ordering is lexicographic; "today" here is
|
|
* 2026-07-10 (the demo business date).
|
|
*/
|
|
const TODAY = '2026-07-10'
|
|
|
|
const b = (id: string, batchNo: string, expiryDate: string | undefined, onHand = 10): BatchStock => ({
|
|
id, itemId: 'i-107', batchNo, ...(expiryDate !== undefined ? { expiryDate } : {}), onHand,
|
|
})
|
|
|
|
describe('S5 FEFO — sortFefo', () => {
|
|
it('orders earliest expiry first, undated lots last', () => {
|
|
const sorted = sortFefo([
|
|
b('c', 'B-C', '2026-12-31'),
|
|
b('u', 'B-U', undefined),
|
|
b('a', 'B-A', '2026-07-15'),
|
|
b('x', 'B-X', '2026-07-05'), // already expired — still earliest
|
|
])
|
|
expect(sorted.map((x) => x.id)).toEqual(['x', 'a', 'c', 'u'])
|
|
})
|
|
|
|
it('breaks an exact expiry tie by batch_no and does not mutate the input', () => {
|
|
const input = [b('2', 'B-Z', '2026-07-20'), b('1', 'B-A', '2026-07-20')]
|
|
const sorted = sortFefo(input)
|
|
expect(sorted.map((x) => x.batchNo)).toEqual(['B-A', 'B-Z'])
|
|
expect(input.map((x) => x.id)).toEqual(['2', '1']) // original order intact
|
|
})
|
|
})
|
|
|
|
describe('S5 FEFO — isExpired', () => {
|
|
it('is true only for a dated lot strictly before today', () => {
|
|
expect(isExpired({ expiryDate: '2026-07-09' }, TODAY)).toBe(true)
|
|
expect(isExpired({ expiryDate: TODAY }, TODAY)).toBe(false) // expires today = still sellable
|
|
expect(isExpired({ expiryDate: '2026-07-11' }, TODAY)).toBe(false)
|
|
expect(isExpired({ expiryDate: undefined }, TODAY)).toBe(false) // undated never expires
|
|
})
|
|
})
|
|
|
|
describe('S5 FEFO — pickFefoBatch', () => {
|
|
it('preselects the earliest NON-expired lot, skipping expired ones', () => {
|
|
const pick = pickFefoBatch([
|
|
b('x', 'B-X', '2026-07-05'), // expired — skipped
|
|
b('a', 'B-A', '2026-07-15'), // earliest sellable
|
|
b('c', 'B-C', '2026-12-31'),
|
|
], TODAY)
|
|
expect(pick?.id).toBe('a')
|
|
})
|
|
|
|
it('falls back to the earliest-expiring lot when every lot is expired', () => {
|
|
const pick = pickFefoBatch([
|
|
b('y', 'B-Y', '2026-07-08'),
|
|
b('x', 'B-X', '2026-07-01'), // earliest of the expired
|
|
], TODAY)
|
|
expect(pick?.id).toBe('x')
|
|
})
|
|
|
|
it('returns undefined for an empty list', () => {
|
|
expect(pickFefoBatch([], TODAY)).toBeUndefined()
|
|
})
|
|
})
|