Functional Weave
Code in Rust

inventory.expiry-fefo@1.0.0

impl/python.py

1,649 bytes · the Python implementation · view raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

from typing import List, Sequence

from .dates_days_between import days_between  ← from dates.days-between ^1.0.0 · built alongside by fune
from .inventory_expiry_fefo_types import FefoPick, PickLine, StockBatch


def _whole_units(name: str, value: int) -> None:
    if isinstance(value, bool) or not isinstance(value, int) or value < 0:
        raise ValueError("%s must be a whole number, not negative, received %r" % (name, value))


def fefo_pick(batches: Sequence[StockBatch], quantity: int, as_at: str, min_shelf_life_days: int) -> FefoPick:
    """Pick ``quantity`` units earliest expiry first, from batches with at least
    ``min_shelf_life_days`` of life left on ``as_at``. Short stock is a
    shortfall, not an error.
    """
    _whole_units("quantity", quantity)
    _whole_units("minShelfLifeDays", min_shelf_life_days)
    dated = []
    for batch in batches:
        _whole_units('quantity of batch "%s"' % batch.batch, batch.quantity)
        dated.append((days_between(as_at, batch.expiry), batch))
    # sorted() is stable, so equal expiry dates keep their order.
    dated = sorted(dated, key=lambda pair: pair[0])
    lines: List[PickLine] = []
    skipped: List[str] = []
    remaining = quantity
    for life, batch in dated:
        if batch.quantity == 0:
            continue
        if life < min_shelf_life_days:
            skipped.append(batch.batch)
            continue
        if remaining == 0:
            continue
        take = min(remaining, batch.quantity)
        lines.append(PickLine(batch=batch.batch, expiry=batch.expiry, quantity=take))
        remaining -= take
    return FefoPick(lines=lines, picked=quantity - remaining, shortfall=remaining, skipped=skipped)