Тони обнови решението на 04.11.2011 00:11 (преди около 13 години)
+require 'bigdecimal'
+require 'bigdecimal/util'
+
+class Coupon
+ attr_reader :name, :kind, :amount
+
+ def initialize(name, kind, amount)
+ @name = name
+ @kind = kind
+ @amount = amount
+ end
+
+ def coupon_discount(total)
+ if kind == :percent
+ total * (@amount / '100'.to_d)
+ else
+ @amount.to_d > total ? total : @amount.to_d
+ end
+ end
+
+ def invoice_info(total)
+ discount = coupon_discount(total)
+ if kind == :amount
+ info = "| Coupon " + @name + " - " + sprintf("%.2f", @amount.to_d) + " off"
+ info += " " * (33 - @name.length - sprintf("%.2f", @amount.to_d).length)
+ else
+ info = "| Coupon " + @name + " - " + @amount.to_s
+ info += "% off" + " " * (32 - @name.length - @amount.to_s.length)
+ end
+ info += "|" + " " * (5 - discount.floor.to_s.length) + "-"
+ info + sprintf("%.2f", discount) + " |\n"
+ end
+end
+
+class Product
+ attr_reader :name, :price, :prom
+
+ def initialize(name, price, prom)
+ @name = name
+ @price = price
+ if prom
+ args = prom.flatten
+ if args[0] == :get_one_free
+ @prom = Promotion.new(*args)
+ else
+ @prom = Promotion.new(args[0], args[1].flatten)
+ end
+ end
+ end
+
+ def multi_price(product_count)
+ @price * product_count
+ end
+
+ def discount(product_count)
+ @prom ? @prom.discount(@price, product_count) : "0".to_d
+ end
+
+ def invoice_info(product_count)
+ info = "| " + @name
+ info += " " * (46 - @name.length - product_count.to_s.length)
+ multi = multi_price(product_count)
+ info += product_count.to_s + " |" + " " * (6 - multi.floor.to_s.length)
+ info += sprintf("%.2f", multi) + " |\n"
+ @prom ? info + @prom.invoice_info(@price, product_count) : info
+ end
+end
+
+class Promotion
+ attr_reader :kind, :spec
+
+ def initialize(kind, spec)
+ @kind = kind
+ @spec = spec
+ end
+
+ def discount(price, count)
+ if @kind == :get_one_free
+ (count / @spec) * price
+ elsif @kind == :package
+ ((count - count % @spec[0]) * price) * (@spec[1] / '100'.to_d)
+ else
+ if count > @spec[0]
+ ((count - @spec[0]) * price) * (@spec[1] / '100'.to_d)
+ else
+ "0".to_d
+ end
+ end
+ end
+
+ def get_one_free_info(price, count, discount)
+ info = "| (buy " + (@spec - 1).to_s
+ info += ", get 1 free)" + " " * (27 - (@spec - 1).to_s.length)
+ info += "|" + " " * (5 - discount.floor.to_s.length) + "-"
+ info + sprintf("%.2f", discount) + " |\n"
+ end
+
+ def package_info(price, count, discount)
+ info = "| (get " + @spec[1].to_s + "% off for every " + @spec[0].to_s
+ info += ")" + " " * (23 - @spec[1].to_s.length - @spec[0].to_s.length)
+ info += "|" + " " * (5 - discount.floor.to_s.length) + "-"
+ info + sprintf("%.2f", discount) + " |\n"
+ end
+
+ def threshold_info(price, count, discount)
+ info = "| (" + @spec[1].to_s + "% off of every after the "
+ info += @spec[0].to_s
+ info += number_suffix(@spec[0] % 10) + ")"
+ info += " " * (16 - @spec[1].to_s.length - @spec[0].to_s.length)
+ info += "|" + " " * (5 - discount.floor.to_s.length) + "-"
+ info + sprintf("%.2f", discount) + " |\n"
+ end
+
+ def number_suffix(digit)
+ if digit == 1
+ "st"
+ elsif digit == 2
+ "nd"
+ elsif digit == 3
+ "rd"
+ else
+ "th"
+ end
+ end
+
+ def invoice_info(price, count)
+ discount = discount(price, count)
+ if discount != 0
+ if @kind == :get_one_free
+ get_one_free_info(price, count, discount)
+ elsif @kind == :package
+ package_info(price, count, discount)
+ else
+ threshold_info(price, count, discount)
+ end
+ else
+ ""
+ end
+ end
+end
+
+class Inventory
+ attr_reader :products, :coupons
+
+ def initialize
+ @products = {}
+ @coupons = {}
+ end
+
+ def register(product_name, price_string, promotion = nil)
+ price = price_string.to_d
+ if @products.include? product_name or
+ product_name.length > 40 or price < 0.01 or price > 999.99
+ raise "Invalid product name/price - " + product_name + "/" + price_string + "."
+ end
+ @products[product_name] = Product.new(product_name, price, promotion)
+ end
+
+ def register_coupon(name, kind)
+ if @coupons.include? name
+ raise "Existing coupon - " + name + "."
+ end
+ @coupons[name] = Coupon.new(name, *kind.flatten)
+ end
+
+ def new_cart
+ Cart.new(self)
+ end
+end
+
+class Cart
+ def initialize(inventory)
+ @inventory = inventory
+ @purchased = Hash.new(0)
+ end
+
+ def add(product_name, qty = 1)
+ if not @inventory.products.include? product_name or
+ @purchased[product_name] + qty < 1 or @purchased[product_name] + qty > 99
+ raise "Invalid product (" + product_name + ") count - "
+ + (product_name[product_name] + qty).to_s + "."
+ end
+ @purchased[product_name] += qty
+ end
+
+ def total_no_coupon
+ no_coupon = "0".to_d
+ @purchased.each do |product_name, qty|
+ multi_price = @inventory.products[product_name].multi_price(qty)
+ discount = @inventory.products[product_name].discount(qty)
+ no_coupon += multi_price - discount
+ end
+ no_coupon
+ end
+
+ def total
+ total_price = total_no_coupon
+ total_price -= @used_coupon.coupon_discount(total_price) if @used_coupon
+ total_price < 0 ? "0".to_d : total_price
+ end
+
+ def use(coupon_name)
+ if not @inventory.coupons.include? coupon_name or @used_coupon
+ raise "Invalid coupon - " + coupon_name + "."
+ end
+ @used_coupon = @inventory.coupons[coupon_name]
+ end
+
+ def invoice_header
+ header = "+" + "-" * 48 + "+----------+\n"
+ header += "| Name" + " " * 39 + "qty | price |\n"
+ header + "+" + "-" * 48 + "+----------+\n"
+ end
+
+ def invoice_footer(final_price)
+ footer = "+" + "-" * 48 + "+----------+\n"
+ footer += "| TOTAL" + " " * 42 + "|"
+ footer += " " * (6 - final_price.floor.to_s.length)
+ footer += sprintf("%.2f", final_price) + " |\n"
+ footer + "+" + "-" * 48 + "+----------+\n"
+ end
+
+ def invoice
+ invoice = invoice_header
+ @purchased.each do |product_name, qty|
+ invoice += @inventory.products[product_name].invoice_info(qty)
+ end
+ final_price = total_no_coupon
+ if @used_coupon
+ invoice += @used_coupon.invoice_info final_price
+ final_price -= @used_coupon.coupon_discount final_price
+ end
+ invoice += invoice_footer(final_price < 0 ? "0".to_d : final_price)
+ end
+end