use super::funejson::Value; use super::logistics_container_fit_data::CONTAINERS; /// How many identical cartons fit in a container or trailer: the best block /// stow over the allowed orientations that pass the door, capped by payload. /// /// # Panics /// Panics on a dimension or weight below 1, an unknown container, a payload /// limit above the container's rating, or a carton no orientation fits. pub fn container_fit( container: &str, length_mm: i64, width_mm: i64, height_mm: i64, carton_grams: i64, keep_upright: bool, max_payload_kg: Option, ) -> ContainerFit { for d in [length_mm, width_mm, height_mm] { if d < 1 { panic!("carton dimensions must be 1 mm or more, received {}", d); } } if carton_grams < 1 { panic!("cartonGrams must be 1 or more, received {}", carton_grams); } let spec = match CONTAINERS.iter().find(|c| c.code == container) { Some(s) => s, None => { let codes: Vec<&str> = CONTAINERS.iter().map(|c| c.code).collect(); panic!("unknown container \"{}\": expected {}", container, codes.join(", ")); } }; let mut payload_kg = spec.max_payload_kg; if let Some(limit) = max_payload_kg { if limit < 1 { panic!("maxPayloadKg must be 1 or more, received {}", limit); } if limit > spec.max_payload_kg { panic!( "maxPayloadKg must not exceed {} kg for {}, received {}", spec.max_payload_kg, container, limit ); } payload_kg = limit; } let (l, w, h) = (length_mm, width_mm, height_mm); // Upright first (as given, then turned on the floor), then the four tipped ones. let orientations: Vec<(i64, i64, i64)> = if keep_upright { vec![(l, w, h), (w, l, h)] } else { vec![(l, w, h), (w, l, h), (l, h, w), (h, l, w), (w, h, l), (h, w, l)] }; let mut best: Option<(i64, i64, i64, (i64, i64, i64))> = None; let mut best_count = 0; for o in orientations { let (a, b, c) = o; if a > spec.length_mm || b > spec.width_mm || c > spec.height_mm { continue; } // End-loaded containers: the carton's cross-section has to pass the door. if let Some(door) = spec.door_width_mm { if b > door { continue; } } if let Some(door) = spec.door_height_mm { if c > door { continue; } } let along = spec.length_mm / a; let across = spec.width_mm / b; let layers = spec.height_mm / c; let count = along * across * layers; if best.is_none() || count > best_count { best = Some((along, across, layers, o)); best_count = count; } } let (along, across, layers, placed) = match best { Some(b) => b, None => panic!("carton does not fit in {} in any allowed orientation", container), }; let weight_limit = payload_kg * 1000 / carton_grams; let cartons = best_count.min(weight_limit); let space = spec.length_mm * spec.width_mm * spec.height_mm; ContainerFit { container: container.to_string(), cartons, space_limit: best_count, weight_limit, limited_by: if weight_limit < best_count { "weight" } else { "space" }.to_string(), along_length: along, across_width: across, layers, placed_length_mm: placed.0, placed_width_mm: placed.1, placed_height_mm: placed.2, cargo_grams: cartons * carton_grams, volume_used_basis_points: cartons * l * w * h * 10000 / space, } } pub fn container_fit_to_value(f: &ContainerFit) -> Value { Value::obj(vec![ ("container", Value::str(&f.container)), ("cartons", Value::Int(f.cartons)), ("spaceLimit", Value::Int(f.space_limit)), ("weightLimit", Value::Int(f.weight_limit)), ("limitedBy", Value::str(&f.limited_by)), ("alongLength", Value::Int(f.along_length)), ("acrossWidth", Value::Int(f.across_width)), ("layers", Value::Int(f.layers)), ("placedLengthMm", Value::Int(f.placed_length_mm)), ("placedWidthMm", Value::Int(f.placed_width_mm)), ("placedHeightMm", Value::Int(f.placed_height_mm)), ("cargoGrams", Value::Int(f.cargo_grams)), ("volumeUsedBasisPoints", Value::Int(f.volume_used_basis_points)), ]) } pub fn fune_vector(args: &[Value]) -> Value { let limit = if args[6].is_null() { None } else { Some(args[6].as_i64()) }; container_fit_to_value(&container_fit( args[0].as_str(), args[1].as_i64(), args[2].as_i64(), args[3].as_i64(), args[4].as_i64(), args[5].as_bool(), limit, )) }