Николай обнови решението на 07.11.2011 02:37 (преди над 12 години)
+require 'bigdecimal'
+require 'bigdecimal/util' # добавя String#to_d
+
+class GetOneFreePromotion
+ def initialize(free_number)
+ @free_number = free_number
+ end
+
+ def discount(products_count, product_price)
+ affected_products = products_count / @free_number
+ affected_products * product_price
+ end
+
+ def discount_message
+ "(buy #{@free_number - 1}, get 1 free)"
+ end
+end
+
+class PackagePromotion
+ def initialize(package_size, discount_percent)
+ @package_size = package_size
+ @discount_percent = discount_percent
+ end
+
+ def discount(products_count, product_price)
+ affected_products = products_count - products_count % @package_size
+ affected_products * product_price * @discount_percent / 100.0
+ end
+
+ def discount_message
+ "(get #{@discount_percent}% off for every #{@package_size})"
+ end
+end
+
+class ThresholdPromotion
+ def initialize(threshold_size, discount_percent)
+ @threshold_size = threshold_size
+ @discount_percent = discount_percent
+ end
+
+ def discount(products_count, product_price)
+ if products_count <= @threshold_size
+ return 0.0
+ end
+
+ affected_products = products_count - @threshold_size
+ affected_products * product_price * @discount_percent / 100.0
+ end
+
+ def discount_message
+ position = if @threshold_size == 1 then 'st'
+ elsif @threshold_size == 2 then 'nd'
+ elsif @threshold_size == 3 then 'rd'
+ else 'th'
+ end
+
+ "(#{@discount_percent}% off of every after the #{@threshold_size}#{position})"
+ end
+end
+
+class PercentCoupon
+ def initialize(discount_percent)
+ @discount_percent = discount_percent
+ end
+
+ def discount(total_cost)
+ total_cost * @discount_percent / 100.0
+ end
+
+ def discount_message(name)
+ "Coupon #{name} - #{@discount_percent}% off"
+ end
+end
+
+class AmountCoupon
+ def initialize(discount_amount)
+ @discount_amount = discount_amount
+ end
+
+ def discount(total_cost)
+ if total_cost <= @discount_amount
+ total_cost
+ else
+ @discount_amount
+ end
+ end
+
+ def discount_message(name)
+ "Coupon #{name} - #{sprintf("%.2f", @discount_amount)} off"
+ end
+end
+
+
+class ShoppingCart
+ def initialize(inventory)
+ @inventory = inventory
+ @purchases = Hash.new(0)
+ @coupon_used = nil
+ end
+
+ def add(product, quantity = 1)
+ incorrect_product = !@inventory.has_product?(product)
+ incorrect_quantity = incorrect_quantity? product, quantity
+ if incorrect_product or incorrect_quantity
+ raise "Invalid arguments in add in cart"
+ end
+
+ @purchases[product] += quantity
+ end
+
+ def use(name)
+ if not (@inventory.get_coupon name).nil? and @coupon_used.nil?
+ @coupon_used = name
+ else
+ raise 'Already used coupon!'
+ end
+ end
+
+ def incorrect_quantity?(name, quantity)
+ not (1..99).include? @purchases[name] + quantity
+ end
+
+ def invoice
+ result = "+------------------------------------------------+----------+
+| Name qty | price |
++------------------------------------------------+----------+\n"
+
+ @purchases.each { |key, value| result += print_purchase_info(key, value) }
+ if not @coupon_used.nil?
+ result += print_coupon_info
+ end
+
+ result += "+------------------------------------------------+----------+
+| TOTAL | #{sprintf("%8.2f", total)} |
++------------------------------------------------+----------+\n"
+
+ result
+ end
+
+ def print_coupon_info
+ coupon = @inventory.get_coupon @coupon_used
+ discount = coupon.discount total_without_coupon
+
+ result = ""
+ if discount > 0.0
+ discount_message = sprintf("%-47s", (coupon.discount_message @coupon_used))
+ result = "| #{discount_message}| #{sprintf("%8.2f", -discount)} |\n"
+ end
+
+ result
+ end
+
+ def print_purchase_info(product, quantity)
+ product_string = sprintf("%-42s", product)
+ quantity_string = sprintf("%3d", quantity)
+ cost_string = sprintf("%8.2f", quantity * (@inventory.product_price product))
+ result = "| #{product_string} #{quantity_string} | #{cost_string} |\n"
+
+ if not (@inventory.get_promotion product).nil?
+ result += print_discount_info product, quantity
+ end
+
+ result
+ end
+
+ def print_discount_info(product, quantity)
+ promotion = @inventory.get_promotion product
+ discount = promotion.discount quantity, (@inventory.product_price product)
+
+ result = ""
+ if discount > 0
+ message_string = sprintf("%-44s", promotion.discount_message)
+ discount_string = sprintf("%8.2f", -discount)
+ result = "| #{message_string} | #{discount_string} |\n"
+ end
+ result
+ end
+
+ def total_without_coupon
+ result = "0.0".to_d
+ @purchases.each do |product, quantity|
+ product_price = @inventory.product_price product
+ result += quantity * product_price
+
+ if not (@inventory.get_promotion product).nil?
+ result -= (@inventory.get_promotion product).discount quantity, product_price
+ end
+ end
+
+ result
+ end
+
+ def total
+ result = total_without_coupon
+ if not @coupon_used.nil?
+ coupon = @inventory.get_coupon @coupon_used
+ discount = coupon.discount total_without_coupon
+ result -= discount
+ end
+
+ result
+ end
+end
+
+class Inventory
+ def initialize
+ @products = {}
+ @promotions = {}
+ @coupons = {}
+ end
+
+ def new_cart
+ ShoppingCart.new self
+ end
+
+ def register(name, price, preferences = {})
+ incorrect_name = name.size > 40 or @products.has_key? name
+ incorrect_price = !((price.to_d <= 999.99) and (price.to_d >= 0.01))
+ if incorrect_name or incorrect_price
+ raise "Ivalid arguments in register of product in invetory!"
+ end
+
+ @products[name] = price.to_d
+ register_promotions name, preferences
+ end
+
+ def register_promotions(name, preferences = {})
+ if preferences.has_key? :get_one_free
+ @promotions[name] = GetOneFreePromotion.new preferences[:get_one_free]
+ elsif preferences.has_key? :package
+ package_size, discount_percent = preferences[:package].flatten
+ @promotions[name] = PackagePromotion.new package_size, discount_percent
+ elsif preferences.has_key? :threshold
+ threshold_size, discount_percent = preferences[:threshold].flatten
+ @promotions[name] = ThresholdPromotion.new threshold_size, discount_percent
+ end
+ end
+
+ def register_coupon(name, preferences = {})
+ if @coupons.has_key? name
+ raise "Duplicate coupon"
+ end
+
+ if preferences.has_key? :percent
+ @coupons[name] = PercentCoupon.new preferences[:percent]
+ elsif preferences.has_key? :amount
+ @coupons[name] = AmountCoupon.new preferences[:amount].to_d
+ end
+ end
+
+ def get_coupon(coupon_name)
+ @coupons[coupon_name]
+ end
+
+ def get_promotion(product_name)
+ @promotions[product_name]
+ end
+
+ def has_product?(product_name)
+ @products.has_key? product_name
+ end
+
+ def product_price(product_name)
+ @products[product_name]
+ end
+end
+