Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::dates_days_between::days_between; ← from dates.days-between ^1.0.0 · built alongside by fune
fn whole_units(name: &str, value: i64) {
if value < 0 {
panic!("{} must be a whole number, not negative, received {}", name, value);
}
}
/// 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.
///
/// # Panics
/// Panics on a negative quantity or shelf life, or a malformed date.
pub fn fefo_pick(batches: &[StockBatch], quantity: i64, as_at: &str, min_shelf_life_days: i64) -> FefoPick {
whole_units("quantity", quantity);
whole_units("minShelfLifeDays", min_shelf_life_days);
let mut dated: Vec<(i64, &StockBatch)> = batches
.iter()
.map(|batch| {
whole_units(&format!("quantity of batch \"{}\"", batch.batch), batch.quantity);
(days_between(as_at, &batch.expiry), batch)
})
.collect();
// sort_by_key is stable, so equal expiry dates keep their order.
dated.sort_by_key(|pair| pair.0);
let mut lines = Vec::new();
let mut skipped = Vec::new();
let mut remaining = quantity;
for (life, batch) in dated {
if batch.quantity == 0 {
continue;
}
if life < min_shelf_life_days {
skipped.push(batch.batch.clone());
continue;
}
if remaining == 0 {
continue;
}
let take = remaining.min(batch.quantity);
lines.push(PickLine {
batch: batch.batch.clone(),
expiry: batch.expiry.clone(),
quantity: take,
});
remaining -= take;
}
FefoPick {
lines,
picked: quantity - remaining,
shortfall: remaining,
skipped,
}
}
pub fn stock_batch_from_value(v: &Value) -> StockBatch {
StockBatch {
batch: v.get("batch").as_str().to_string(),
expiry: v.get("expiry").as_str().to_string(),
quantity: v.get("quantity").as_i64(),
}
}
pub fn fefo_pick_to_value(result: &FefoPick) -> Value {
Value::obj(vec![
(
"lines",
Value::Arr(
result
.lines
.iter()
.map(|l| {
Value::obj(vec![
("batch", Value::str(&l.batch)),
("expiry", Value::str(&l.expiry)),
("quantity", Value::Int(l.quantity)),
])
})
.collect(),
),
),
("picked", Value::Int(result.picked)),
("shortfall", Value::Int(result.shortfall)),
("skipped", Value::Arr(result.skipped.iter().map(|s| Value::str(s)).collect())),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
if let Value::Float(f) = &args[1] {
panic!("quantity must be a whole number, not negative, received {}", f);
}
let batches: Vec<StockBatch> = args[0].as_arr().iter().map(stock_batch_from_value).collect();
fefo_pick_to_value(&fefo_pick(&batches, args[1].as_i64(), args[2].as_str(), args[3].as_i64()))
}