invoice.ry

module invoice
  purpose: Compute invoice totals with a percentage discount, exact to the cent.

public type Line
  purpose: One billable line of an invoice.
  has description: Text
  has quantity: Integer where quantity is at least 1
  has unit_price: Decimal where unit_price is at least 0
end

public type Invoice
  purpose: A set of lines billed to one customer.
  has customer: Text
  has lines: List of Line
  has discount_percent: Decimal
    where discount_percent is at least 0
    and discount_percent is at most 100
end

public function line_total(line: Line) returns Decimal
  purpose: Quantity times unit price for one line.
  example: line_total(Line(description: "pen", quantity: 3, unit_price: 1.50)) is 4.50

  return line.quantity.to_decimal() * line.unit_price
end

public function subtotal(invoice: Invoice) returns Decimal
  purpose: Sum of all line totals before the discount.
  see also: total

  return for each line in invoice.lines sum line_total(line)
end

public function total(invoice: Invoice) returns Decimal
  purpose: Subtotal after the percentage discount, rounded to cents.
  see also: subtotal

  let before_discount be subtotal(invoice)
  let discount be before_discount * invoice.discount_percent / 100
  return (before_discount - discount).rounded(2)
end

test "a ten percent discount is taken from the subtotal"
  let pens be Line(description: "pen", quantity: 10, unit_price: 1.00)
  let order be Invoice(customer: "ACME", lines: [pens], discount_percent: 10)
  check subtotal(order) is 10.00
  check total(order) is 9.00
end

test "an invoice without lines totals zero"
  let empty be Invoice(customer: "ACME", lines: [], discount_percent: 0)
  check total(empty) is 0
end