Николай обнови решението на 07.11.2011 14:06 (преди около 13 години)
+require 'bigdecimal'
+require 'bigdecimal/util'
+module Constants
+ LOWER_PRICE_BOUND = BigDecimal('0.01')
+ HIGHER_PRICE_BOUND = BigDecimal('999.99')
+ SEPARATOR = '+'+('-' * 48) + '+' + ('-'*10) + '+' + "\n"
+ VERTICAL_LINE = '|'
+ PRODUCT_MAX_LENGHT = 40
+end
+
+module CartHelper
+
+ def format_price(digit)
+ sprintf("%0.2f", digit)
+ end
+
+ def is_quant_invalid?(quantity)
+ quantity <= 0 or quantity > 99
+ end
+
+ def threshold_ending(bound_elem)
+ if bound_elem.to_s.end_with?('1')
+ 'st)'
+ elsif bound_elem.to_s.end_with?('2')
+ 'nd)'
+ elsif bound_elem.to_s.end_with?('3')
+ 'rd)'
+ else bound_elem > 3
+ 'th)'
+ end
+ end
+
+ def summed_price(product_name, product_quantity)
+ format_price((products_in_inv.find{|y|y.name == product_name}.price)*product_quantity)
+ end
+
+ def calc_money(product, quantity, type)
+ if type == :get_one_free
+ product.price*(quantity / product.promotions[:get_one_free])
+ elsif type == :package
+ perc = (product.promotions[:package].values.first().to_f / 100)
+ format_price((product.price * perc) * product.promotions[:package].keys.first() *
+ (quantity / product.promotions[:package].keys.first())).to_d
+ elsif type == :threshold
+ threshold_price(product, quantity)
+ end
+ end
+
+ def check_promo?(product, quantity, type)
+ if type == :get_one_free
+ product.promotions.key?(:get_one_free)&&quantity>=product.promotions[:get_one_free]
+ elsif type == :package
+ product.promotions.key?(:package)&&quantity>=product.promotions[:package].keys.first()
+ elsif type == :threshold
+ product.promotions.key?(:threshold)&&
+ quantity >= product.promotions[:threshold].keys.first()
+ end
+ end
+
+ def package_msg(product)
+ "(get " + first_prod_to_s(product)+\
+ "% off for every " + product.promotions[:package].keys.first().to_s + ")"
+ end
+
+ def threshold_msg(product)
+ elem = product.promotions[:threshold].keys.first()
+ "("+first_prod_to_s(product)+"% off of every after the "+elem.to_s+threshold_ending(elem)
+ end
+
+ def get_one_free_msg(product)
+ "(buy " + (product.promotions.values.first() - 1).to_s + ", get 1 free)"
+ end
+
+ def threshold_price(product, quantity)
+ diff = format_price(quantity-product.promotions[:threshold].keys.first())
+ perc = (product.promotions[:threshold].values.first().to_f / 100)
+ format_price(BigDecimal(diff) * ((product.price * perc))).to_d
+ end
+end
+
+class Inventory
+ include Constants, CartHelper
+ attr_accessor :products, :coupons
+
+ def register_coupon(coupon_name, discount)
+ coupons << Coupon.new(coupon_name, discount)
+ end
+
+ def initialize()
+ @products = []
+ @coupons = []
+ end
+
+ def new_cart()
+ Cart.new(products, coupons)
+ end
+
+ def validate_registration(product_name, product_price)
+ if is_product_name_correct?(product_name) or not is_price_in_range?(product_price)
+ raise RuntimeError.new("Invalid parameters passed.")
+ end
+ end
+
+ def is_product_name_correct?(product_name)
+ products.one?{ |x| x.name == product_name} or product_name.length>PRODUCT_MAX_LENGHT
+ end
+
+ def is_price_in_range?(price)
+ BigDecimal(price) >= LOWER_PRICE_BOUND and BigDecimal(price) <= HIGHER_PRICE_BOUND
+ end
+
+ def register(product_name, product_price, promo = {})
+ validate_registration(product_name, format_price(product_price))
+ products << Product.new(product_name, BigDecimal(format_price(product_price)), promo)
+ end
+end
+
+
+class Cart
+ include Constants, CartHelper
+ attr_accessor :products_in_inv, :products_in_cart, :coupons, :coupon_sum,
+ :cart_sum, :promo_sum, :promo_products
+
+ def initialize(products, coupons)
+ @products_in_inv = products
+ @products_in_cart = {}
+ @promo_products = {}
+ @coupons = coupons
+ @coupon_sum = BigDecimal('0.00')
+ @cart_sum = BigDecimal('0.00')
+ @promo_sum = BigDecimal('0.00')
+ end
+
+ def add(product_name, quantity = 1)
+ if is_quant_invalid?(quantity) or not products_in_inv.one?{|x| x.name == product_name}
+ raise "invalid arguments (cart)"
+ end
+ if products_in_cart[product_name] == nil
+ products_in_cart[product_name] = quantity
+ else
+ if products_in_cart[product_name] + quantity > 99
+ raise "invalid argmunts (cart)"
+ end
+ products_in_cart[product_name] += quantity
+ end
+ cache_saved_prices
+ end
+
+ def total
+ bill_after_promotions = cart_sum - (promo_sum)
+ bill_after_coupons = bill_after_promotions > coupon_sum ?
+ bill_after_promotions - coupon_sum : BigDecimal('0.00')
+ @promo_sum = BigDecimal('0.00')
+ bill_after_coupons
+ end
+
+ def cache_saved_prices
+ cart_sum = BigDecimal('0.00')
+ saved_sum = BigDecimal('0.00')
+ products_in_cart.keys.each do |x|
+ cart_sum += products_in_inv.find {|y| y.name == x}.price * products_in_cart[x]
+ saved_sum = apply_promo(products_in_inv.find {|y| y.name == x}, products_in_cart[x])
+ promo_products[x] = saved_sum
+ end
+ @cart_sum = cart_sum
+ @promo_sum += saved_sum
+ end
+
+ def apply_promo(product, quant_in_cart)
+ saved_money = BigDecimal('0.00')
+ if(check_promo?(product, quant_in_cart, :get_one_free))
+ saved_money = calc_money(product, quant_in_cart, :get_one_free)
+ elsif(check_promo?(product, quant_in_cart, :package))
+ saved_money = calc_money(product, quant_in_cart, :package)
+ elsif(check_promo?(product, quant_in_cart, :threshold))
+ saved_money = calc_money(product, quant_in_cart, :threshold)
+ end
+ saved_money
+ end
+
+
+ def use(coupon_name)
+ rest_sum = cart_sum - promo_sum
+ if(coupons[0].discount.keys.first() == :percent)
+ @coupon_sum = format_price(rest_sum *
+ BigDecimal((coupons[0].discount.values.first().to_f / 100).to_s)).to_d
+ else
+ @coupon_sum = BigDecimal(format_price(coupons[0].discount.values.first()))-rest_sum
+ end
+ promo_products[coupons[0].name] = @coupon_sum
+ @coupon_sum
+ end
+
+ def invoice
+ receipt_msg = SEPARATOR + "| Name qty | price |\n"+ SEPARATOR
+ products_in_cart.each do |key, value|
+ receipt_msg += "| " + key.to_s + (" ") + value.to_s + " |"+\
+ (" ") + summed_price(key, value) + " |\n"
+ receipt_msg += construct_promotion_msg(key)
+ end
+ receipt_msg += construct_coupon_msg if(coupons != [])
+ footer = SEPARATOR + "| TOTAL | " + format_price(total) + " |\n" + SEPARATOR
+ receipt_msg += footer
+ end
+
+ def first_prod_to_s(product)
+ product.promotions.values.first().values.first().to_s
+ end
+
+ def construct_promotion_msg(key)
+ msg, product = "|" + " ", products_in_inv.find{ |x| x.name == key}
+ if(product.promotions.key?(:get_one_free)) then ms = get_one_free_msg(product)
+ elsif(product.promotions.key?(:package)) then ms = package_msg(product)
+ elsif(product.promotions.key?(:threshold))
+ ms = threshold_msg(product)
+ else return ""
+ end
+ msg += ms + " | -" + format_price(promo_products[product.name]) + " |\n"
+ end
+
+ def construct_coupon_msg
+ msg = "|" + " Coupon " + coupons[0].name
+ if(coupons[0].discount.keys.first() == :percent)
+ ms = " - " + coupons[0].discount[:percent].to_s + "% off"
+ elsif(coupons[0].discount.keys.first() == :amount)
+ ms = " - " + format_price(coupons[0].discount[:amount]) + " off"
+ else
+ return ""
+ end
+ msg += ms + " | -" + format_price(promo_products[coupons[0].name]) + " |\n"
+ end
+end
+
+class Product
+ attr_accessor :name, :price, :promotions
+
+ def initialize(name, price, promotions)
+ @name, @price, @promotions = name, price, promotions
+ end
+end
+
+class Coupon
+ attr_accessor :name, :discount
+
+ def initialize(name, discount)
+ @name, @discount = name, discount
+ end
+end
+
+
+
+