Васил обнови решението на 07.11.2011 16:29 (преди около 13 години)
+require 'bigdecimal'
+require 'bigdecimal/util'
+
+module Ordinalize
+ def ordinalize(number)
+ if (11..13).include?(number % 100)
+ "#{number}th"
+ else
+ case number % 10
+ when 1 then "#{number}st"
+ when 2 then "#{number}nd"
+ when 3 then "#{number}rd"
+ else "#{number}th"
+ end
+ end
+ end
+end
+
+class Promotion
+ attr_accessor :promotion, :name, :value, :action
+
+ include Ordinalize
+
+ def initialize(promotion)
+ name = promotion.keys.first
+ details = promotion.values.first
+ case name
+ when :get_one_free then get_one_free(details)
+ when :package then package(details)
+ when :threshold then threshold(details)
+ when nil then no_promotion
+ else raise "No such promotion!"
+ end
+ end
+
+ def get_one_free(details)
+ value = details
+
+ @name = "(buy #{value - 1}, get 1 free)"
+ @action = lambda { |quantity| quantity / value }
+ end
+
+ def package(details)
+ percentage = details.values.first
+ value = details.keys.first
+ discount = percentage.to_f / 100
+
+ @name = "(get #{percentage}% off for every #{value})"
+ @action = lambda { |qty| ((qty / value).to_int * value) * discount }
+ end
+
+ def threshold(details)
+ percentage = details.values.first
+ value = details.keys.first
+ discount = percentage.to_f / 100
+
+ @name = "(#{percentage}% off of every after the #{ordinalize(value)}) "
+ @action = lambda { |qty| ((qty - value) * discount) }
+ end
+
+ def no_promotion
+ @name = nil
+ @value = nil
+ end
+end
+
+class Product
+ attr_accessor :name, :price, :promotion
+
+ def initialize(name, price, promotion)
+ if valid_product?(name, price)
+ @name = name
+ @price = price.to_d
+ @promotion = Promotion.new(promotion)
+ else
+ raise "Invalid product name or price"
+ end
+ end
+
+ def valid_product?(name, price)
+ valid_name?(name) and valid_price?(price)
+ end
+
+ def valid_name?(name)
+ name.length <= 40
+ end
+
+ def valid_price?(price)
+ price.to_d >= 0.01 and price.to_d <= 999.99
+ end
+end
+
+class Coupon
+ attr_accessor :name, :value
+
+ def initialize(name, value)
+ @name = name
+ case value.keys.first
+ when 'percent' then coupon_percent(value)
+ when 'amount' then coupon_amount(value)
+ else raise "No such coupon!"
+ end
+ end
+
+ def coupon_percent(value)
+
+ end
+
+ def coupon_amount(value)
+
+ end
+
+end
+
+class Inventory
+ attr_accessor :products
+
+ def initialize
+ @products = []
+ end
+
+ def register(name, price, promotion = {})
+ product = Product.new(name, price, promotion)
+ check_product(product)
+ end
+
+ def check_product(product)
+ if valid_product?(product)
+ @products << product
+ else
+ raise "There's already a product with the name '#{product.name}'."
+ end
+ end
+
+ def valid_product?(in_product)
+ @products.select { |product| product.name == in_product.name }.length == 0
+ end
+
+ def new_cart
+ cart = Cart.new(@products)
+ end
+
+ def register_coupon(name, value)
+ @coupon = Coupon.new(name, value)
+ end
+
+end
+
+class Cart
+ attr_accessor :cart, :inventory, :total
+
+ def initialize(inv)
+ @cart = {}
+ @inventory = inv.map { |product| [product.name, product.price, product.promotion] }
+ @total = 0
+ end
+
+ def add(name, quantity=1)
+ unless valid_name? name and valid_quantity? quantity
+ raise "Invalid parameters passed."
+ end
+
+ promotion, price, promotion_action = *get_price_n_promotion(name)
+
+ if name_exists? name
+ quantity = @cart[name].last + quantity
+ end
+
+ @cart[name] = [price, quantity]
+
+ if promotion and promotion_action.call(quantity) != 0
+ @cart[promotion] = [price * (-1), promotion_action.call(quantity)]
+ end
+ end
+
+ def get_price_n_promotion(product_name)
+ product = @inventory.select { |product| product.first == product_name }
+ [product.last.last.name, product.last[1], product.last.last.action]
+ end
+
+ def name_exists?(name)
+ @cart.include? name
+ end
+
+ def valid_quantity?(quantity)
+ quantity > 0 and
+ quantity <= 99 and
+ quantity.integer?
+ end
+
+ def valid_name?(name)
+ @inventory.flatten.include? name
+ end
+
+ def total
+ @total = []
+ @cart.each do |key, value|
+ @total << (value.first * value.last)
+ end
+ @total.inject(0) { |element, another_element| element + another_element }
+ end
+
+ def invoice
+ invoice = Invoice.new(@cart, total)
+ invoice.string
+ end
+
+ def use
+
+ end
+end
+
+class Invoice
+ attr_accessor :line, :columns, :products, :total, :string
+
+ def initialize(cart, total)
+ @string = generate_string(cart, total)
+ end
+
+ def generate_string(cart, total)
+ line +
+ columns +
+ line +
+ products(cart) +
+ line +
+ format_total(total) +
+ line
+ end
+
+ def line
+ @line = '+' + ('-' * 48) + '+' + ('-' * 10) + '+' + "\n"
+ end
+
+ def columns
+ @columns = "| %-37s %8s | %8s |\n" % ["Name", "qty", "price"]
+ end
+
+ def products(cart)
+ string = ""
+ cart.each do |key, value|
+ name, qty, price = key, value.last, value.last * value.first
+
+ if is_promotion? price
+ string += "| %-44s | %8.2f |\n" % ["#{name}", "#{price}"]
+ else
+ string += "| %-37s %8.0d | %8.2f |\n" % ["#{name}", "#{qty}", "#{price}"]
+ end
+ end
+ string
+ end
+
+ def is_promotion?(price)
+ price < 0
+ end
+
+ def format_total(total)
+ @total = "|%-47s | %8.2f |\n" % [" TOTAL", total.to_f]
+ end
+end