///|
fn report_number(value : Int32) -> Json {
Json::number(value.to_double())
}
///|
fn issue_json(issue : PartIssue) -> Json {
Json::object(
Map([
("part", Json::string(issue.part)),
("units_per_kit", report_number(issue.units_per_kit)),
("available_before", report_number(issue.available_before)),
("required", report_number(issue.required)),
("issued", report_number(issue.issued)),
("limits_order", Json::boolean(issue.limits_order)),
]),
)
}
///|
fn order_json(order : OrderPlan) -> Json {
Json::object(
Map([
("id", Json::string(order.id)),
("kit", Json::string(order.kit)),
("requested", report_number(order.requested)),
("buildable", report_number(order.buildable)),
("waiting", report_number(order.requested - order.buildable)),
("items", Json::array(order.items.map(issue_json))),
]),
)
}
///|
fn balance_json(part : PartBalance) -> Json {
Json::object(
Map([
("part", Json::string(part.part)),
("stock", report_number(part.stock)),
("required", report_number(part.required)),
("issued", report_number(part.issued)),
("remaining_stock", report_number(part.remaining_stock)),
("shortage", report_number(part.shortage)),
]),
)
}
///|
fn pack_json(pack : PackCount) -> Json {
Json::object(
Map([
("id", Json::string(pack.id)),
("quantity", report_number(pack.quantity)),
("count", report_number(pack.count)),
("unit_cost_cents", report_number(pack.unit_cost_cents)),
]),
)
}
///|
fn purchase_json(line : PurchaseLine) -> Json {
let fields = Map([
("part", Json::string(line.part)),
("shortage", report_number(line.shortage)),
])
match line.purchase {
None => fields["status"] = Json::string("unsourced")
Some(purchase) => {
fields["status"] = Json::string("priced")
fields["ordered"] = report_number(purchase.ordered)
fields["excess"] = report_number(purchase.ordered - purchase.shortage)
// Amounts can exceed the exact integer range of a JavaScript number.
fields["cost_cents"] = Json::string(purchase.cost_cents.to_string())
fields["packs"] = Json::array(purchase.packs.map(pack_json))
}
}
Json::object(fields)
}
///|
fn plan_json(result : Plan) -> Json {
Json::object(
Map([
("schema", Json::string("benchkitmap/plan-v1")),
("orders", Json::array(result.allocation.orders.map(order_json))),
("parts", Json::array(result.allocation.parts.map(balance_json))),
("purchases", Json::array(result.purchases.map(purchase_json))),
(
"purchase_total_cents",
Json::string(result.purchase_total_cents.to_string()),
),
("purchase_complete", Json::boolean(result.purchase_complete)),
]),
)
}
///|
fn csv_row(output : StringBuilder, fields : Array[String]) -> Unit {
for i, field in fields {
if i > 0 {
output.write_char(',')
}
output.write_char('"')
for c in field.iter() {
if c == '"' {
output.write_char('"')
}
output.write_char(c)
}
output.write_char('"')
}
output.write_string("\r\n")
}
///|
fn pick_csv(result : Plan) -> String {
let output = StringBuilder()
csv_row(output, [
"order", "kit", "requested_kits", "buildable_kits", "part", "required_units",
"pick_units",
])
for order in result.allocation.orders {
for line in order.items {
csv_row(output, [
order.id,
order.kit,
order.requested.to_string(),
order.buildable.to_string(),
line.part,
line.required.to_string(),
line.issued.to_string(),
])
}
}
output.to_string()
}
///|
fn buy_csv(result : Plan) -> String {
let output = StringBuilder()
csv_row(output, [
"part", "shortage_units", "status", "pack", "units_per_pack", "pack_count", "ordered_units",
"pack_price_cents", "line_price_cents",
])
for line in result.purchases {
match line.purchase {
None =>
csv_row(output, [
line.part,
line.shortage.to_string(),
"unsourced",
"",
"",
"",
"",
"",
"",
])
Some(purchase) =>
for pack in purchase.packs {
csv_row(output, [
line.part,
line.shortage.to_string(),
"priced",
pack.id,
pack.quantity.to_string(),
pack.count.to_string(),
(pack.quantity * pack.count).to_string(),
pack.unit_cost_cents.to_string(),
(pack.unit_cost_cents.to_int64() * pack.count.to_int64()).to_string(),
])
}
}
}
output.to_string()
}
///|
/// Serialize a plan created by `plan`. JSON uses strings for aggregate cents.
pub fn render_plan(result : Plan, format : OutputFormat) -> String {
match format {
JsonReport => plan_json(result).stringify(indent=2) + "\n"
PickCsv => pick_csv(result)
BuyCsv => buy_csv(result)
}
}
///|
/// Export kit readiness now and after receiving all priced purchases.
pub fn render_projection(result : Projection) -> String {
let orders = result.orders.map(order => {
Json::object(
Map([
("id", Json::string(order.id)),
("kit", Json::string(order.kit)),
("requested", report_number(order.requested)),
("buildable_now", report_number(order.buildable_now)),
(
"buildable_after_priced_purchases",
report_number(order.buildable_after_priced_purchases),
),
]),
)
})
Json::object(
Map([
("schema", Json::string("benchkitmap/projection-v1")),
("orders", Json::array(orders)),
("purchase_complete", Json::boolean(result.purchase_complete)),
(
"purchase_total_cents",
Json::string(result.purchase_total_cents.to_string()),
),
]),
).stringify(indent=2) +
"\n"
}