[
{"name": "earliest expiry first, expired B3 skipped, B2 and B4 share a date and go in the order given", "args": [[{"batch": "B1", "expiry": "2026-10-10", "quantity": 5}, {"batch": "B2", "expiry": "2026-09-30", "quantity": 10}, {"batch": "B3", "expiry": "2026-09-20", "quantity": 4}, {"batch": "B4", "expiry": "2026-09-30", "quantity": 3}, {"batch": "B5", "expiry": "2026-12-01", "quantity": 20}], 12, "2026-09-23", 0], "expect": {"lines": [{"batch": "B2", "expiry": "2026-09-30", "quantity": 10}, {"batch": "B4", "expiry": "2026-09-30", "quantity": 2}], "picked": 12, "shortfall": 0, "skipped": ["B3"]}},
{"name": "a 10-day minimum shelf life skips B2 and B4 (7 days left) as well", "args": [[{"batch": "B1", "expiry": "2026-10-10", "quantity": 5}, {"batch": "B2", "expiry": "2026-09-30", "quantity": 10}, {"batch": "B3", "expiry": "2026-09-20", "quantity": 4}, {"batch": "B4", "expiry": "2026-09-30", "quantity": 3}, {"batch": "B5", "expiry": "2026-12-01", "quantity": 20}], 12, "2026-09-23", 10], "expect": {"lines": [{"batch": "B1", "expiry": "2026-10-10", "quantity": 5}, {"batch": "B5", "expiry": "2026-12-01", "quantity": 7}], "picked": 12, "shortfall": 0, "skipped": ["B3", "B2", "B4"]}},
{"name": "not enough in date: everything usable is picked and the rest is a shortfall", "args": [[{"batch": "B1", "expiry": "2026-10-10", "quantity": 5}, {"batch": "B2", "expiry": "2026-09-30", "quantity": 10}, {"batch": "B3", "expiry": "2026-09-20", "quantity": 4}, {"batch": "B4", "expiry": "2026-09-30", "quantity": 3}, {"batch": "B5", "expiry": "2026-12-01", "quantity": 20}], 50, "2026-09-23", 0], "expect": {"lines": [{"batch": "B2", "expiry": "2026-09-30", "quantity": 10}, {"batch": "B4", "expiry": "2026-09-30", "quantity": 3}, {"batch": "B1", "expiry": "2026-10-10", "quantity": 5}, {"batch": "B5", "expiry": "2026-12-01", "quantity": 20}], "picked": 38, "shortfall": 12, "skipped": ["B3"]}},
{"name": "stock expiring on the pick date is still usable with no minimum shelf life", "args": [[{"batch": "TODAY", "expiry": "2026-09-23", "quantity": 2}, {"batch": "LATER", "expiry": "2026-09-24", "quantity": 5}], 3, "2026-09-23", 0], "expect": {"lines": [{"batch": "TODAY", "expiry": "2026-09-23", "quantity": 2}, {"batch": "LATER", "expiry": "2026-09-24", "quantity": 1}], "picked": 3, "shortfall": 0, "skipped": []}},
{"name": "a minimum of 1 day excludes stock expiring on the pick date", "args": [[{"batch": "TODAY", "expiry": "2026-09-23", "quantity": 2}, {"batch": "LATER", "expiry": "2026-09-24", "quantity": 5}], 3, "2026-09-23", 1], "expect": {"lines": [{"batch": "LATER", "expiry": "2026-09-24", "quantity": 3}], "picked": 3, "shortfall": 0, "skipped": ["TODAY"]}},
{"name": "shelf life counts across a leap day: 2028-02-19 to 2028-02-29 is 10 days", "args": [[{"batch": "LEAP", "expiry": "2028-02-29", "quantity": 4}], 4, "2028-02-19", 10], "expect": {"lines": [{"batch": "LEAP", "expiry": "2028-02-29", "quantity": 4}], "picked": 4, "shortfall": 0, "skipped": []}},
{"name": "across a non-leap February end, 2027-02-19 to 2027-03-01 is only 10 days, short of an 11-day minimum", "args": [[{"batch": "MARCH", "expiry": "2027-03-01", "quantity": 4}], 4, "2027-02-19", 11], "expect": {"lines": [], "picked": 0, "shortfall": 4, "skipped": ["MARCH"]}},
{"name": "a batch with no units is neither picked nor skipped", "args": [[{"batch": "EMPTY", "expiry": "2026-09-25", "quantity": 0}, {"batch": "OLD", "expiry": "2026-01-01", "quantity": 0}, {"batch": "FULL", "expiry": "2026-09-26", "quantity": 5}], 2, "2026-09-23", 0], "expect": {"lines": [{"batch": "FULL", "expiry": "2026-09-26", "quantity": 2}], "picked": 2, "shortfall": 0, "skipped": []}},
{"name": "asking for nothing picks nothing, but still reports the out-of-date batches", "args": [[{"batch": "B1", "expiry": "2026-10-10", "quantity": 5}, {"batch": "B2", "expiry": "2026-09-30", "quantity": 10}, {"batch": "B3", "expiry": "2026-09-20", "quantity": 4}, {"batch": "B4", "expiry": "2026-09-30", "quantity": 3}, {"batch": "B5", "expiry": "2026-12-01", "quantity": 20}], 0, "2026-09-23", 0], "expect": {"lines": [], "picked": 0, "shortfall": 0, "skipped": ["B3"]}},
{"name": "no batches at all is all shortfall", "args": [[], 7, "2026-09-23", 0], "expect": {"lines": [], "picked": 0, "shortfall": 7, "skipped": []}},
{"name": "a negative request is an error", "args": [[{"batch": "B1", "expiry": "2026-10-10", "quantity": 5}, {"batch": "B2", "expiry": "2026-09-30", "quantity": 10}, {"batch": "B3", "expiry": "2026-09-20", "quantity": 4}, {"batch": "B4", "expiry": "2026-09-30", "quantity": 3}, {"batch": "B5", "expiry": "2026-12-01", "quantity": 20}], -1, "2026-09-23", 0], "expectError": "quantity must be a whole number, not negative"},
{"name": "a fractional request is an error", "args": [[{"batch": "B1", "expiry": "2026-10-10", "quantity": 5}, {"batch": "B2", "expiry": "2026-09-30", "quantity": 10}, {"batch": "B3", "expiry": "2026-09-20", "quantity": 4}, {"batch": "B4", "expiry": "2026-09-30", "quantity": 3}, {"batch": "B5", "expiry": "2026-12-01", "quantity": 20}], 1.5, "2026-09-23", 0], "expectError": "quantity must be a whole number, not negative"},
{"name": "a negative shelf life is an error", "args": [[{"batch": "B1", "expiry": "2026-10-10", "quantity": 5}, {"batch": "B2", "expiry": "2026-09-30", "quantity": 10}, {"batch": "B3", "expiry": "2026-09-20", "quantity": 4}, {"batch": "B4", "expiry": "2026-09-30", "quantity": 3}, {"batch": "B5", "expiry": "2026-12-01", "quantity": 20}], 1, "2026-09-23", -1], "expectError": "minShelfLifeDays must be a whole number, not negative"},
{"name": "a batch with negative stock is an error", "args": [[{"batch": "NEG", "expiry": "2026-10-01", "quantity": -2}], 1, "2026-09-23", 0], "expectError": "quantity of batch \"NEG\" must be a whole number, not negative"},
{"name": "an impossible expiry date is an error", "args": [[{"batch": "BAD", "expiry": "2026-02-30", "quantity": 2}], 1, "2026-09-23", 0], "expectError": "is not a real calendar date"}
]