Functional Weave
Code in Rust

inventory.pallet-fit@1.0.0

impl/rust.rs

4,738 bytes · the Rust 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.

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::inventory_pallet_fit_data::PALLETS;  ← this capability’s own data, compiled from data/pallets.json into the same file by fune build

/// Cartons per layer on a `length` x `width` deck, base `a` x `b`, as the best
/// two-block pattern: one split across the length or the width, each block a
/// grid with the carton one way round.
fn cartons_per_layer(length: i64, width: i64, a: i64, b: i64) -> i64 {
    let mut best = 0;
    let mut i = 0;
    while i * a <= length {
        best = best.max(i * (width / b) + ((length - i * a) / b) * (width / a));
        i += 1;
    }
    let mut j = 0;
    while j * b <= width {
        best = best.max(j * (length / a) + ((width - j * b) / a) * (length / b));
        j += 1;
    }
    best
}

fn positive(name: &str, value: i64) {
    if value <= 0 {
        panic!("{} must be a whole number greater than zero, received {}", name, value);
    }
}

/// The best stack of identical cartons on a UK or EUR pallet within the limits.
///
/// # Panics
/// Panics on a dimension that is not positive, a negative weight or weight
/// limit, or an unknown pallet.
pub fn pallet_fit(carton: &Carton, pallet: &str, max_load_height_mm: i64, max_load_weight_grams: Option<i64>, upright: bool) -> PalletFit {
    positive("lengthMm", carton.length_mm);
    positive("widthMm", carton.width_mm);
    positive("heightMm", carton.height_mm);
    if carton.weight_grams < 0 {
        panic!("weightGrams must be a whole number, not negative, received {}", carton.weight_grams);
    }
    positive("maxLoadHeightMm", max_load_height_mm);
    if let Some(mw) = max_load_weight_grams {
        if mw < 0 {
            panic!("maxLoadWeightGrams must be a whole number, not negative, received {}", mw);
        }
    }
    let deck = match PALLETS.iter().find(|p| p.code == pallet) {
        Some(d) => d,
        None => panic!("unknown pallet \"{}\": expected uk or euro", pallet),
    };

    let (l, w, h) = (carton.length_mm, carton.width_mm, carton.height_mm);
    let choices: Vec<(i64, i64, i64)> = if upright {
        vec![(h, l, w)]
    } else {
        vec![(h, l, w), (w, l, h), (l, w, h)]
    };
    let mut best: Option<PalletFit> = None;
    for (vertical, a, b) in choices {
        let per_layer = cartons_per_layer(deck.length_mm, deck.width_mm, a, b);
        let layers = max_load_height_mm / vertical;
        let mut per_pallet = per_layer * layers;
        let mut limited_by = if per_layer == 0 { "footprint" } else { "height" };
        if let Some(mw) = max_load_weight_grams {
            if per_layer > 0 && carton.weight_grams > 0 {
                let by_weight = mw / carton.weight_grams;
                if by_weight < per_pallet {
                    per_pallet = by_weight;
                    limited_by = "weight";
                }
            }
        }
        let fit = PalletFit {
            pallet_length_mm: deck.length_mm,
            pallet_width_mm: deck.width_mm,
            carton_height_mm: vertical,
            per_layer,
            layers,
            per_pallet,
            limited_by: limited_by.to_string(),
            load_height_mm: if per_layer == 0 { 0 } else { (per_pallet + per_layer - 1) / per_layer * vertical },
            load_weight_grams: per_pallet * carton.weight_grams,
        };
        if best.as_ref().map_or(true, |b| fit.per_pallet > b.per_pallet) {
            best = Some(fit);
        }
    }
    best.unwrap()
}

pub fn carton_from_value(v: &Value) -> Carton {
    Carton {
        length_mm: v.get("lengthMm").as_i64(),
        width_mm: v.get("widthMm").as_i64(),
        height_mm: v.get("heightMm").as_i64(),
        weight_grams: v.get("weightGrams").as_i64(),
    }
}

pub fn pallet_fit_to_value(fit: &PalletFit) -> Value {
    Value::obj(vec![
        ("palletLengthMm", Value::Int(fit.pallet_length_mm)),
        ("palletWidthMm", Value::Int(fit.pallet_width_mm)),
        ("cartonHeightMm", Value::Int(fit.carton_height_mm)),
        ("perLayer", Value::Int(fit.per_layer)),
        ("layers", Value::Int(fit.layers)),
        ("perPallet", Value::Int(fit.per_pallet)),
        ("limitedBy", Value::str(&fit.limited_by)),
        ("loadHeightMm", Value::Int(fit.load_height_mm)),
        ("loadWeightGrams", Value::Int(fit.load_weight_grams)),
    ])
}

pub fn fune_vector(args: &[Value]) -> Value {
    for key in ["lengthMm", "widthMm", "heightMm"] {
        if let Value::Float(f) = args[0].get(key) {
            panic!("{} must be a whole number greater than zero, received {}", key, f);
        }
    }
    let max_weight = if args[3].is_null() { None } else { Some(args[3].as_i64()) };
    pallet_fit_to_value(&pallet_fit(
        &carton_from_value(&args[0]),
        args[1].as_str(),
        args[2].as_i64(),
        max_weight,
        args[4].as_bool(),
    ))
}