Деница обнови решението на 07.11.2011 02:55 (преди около 13 години)
+
+require 'bigdecimal'
+require 'bigdecimal/util'
+
+class Product
+ attr_accessor :name, :price, :promotion
+
+ def initialize(name, price, promotion_hash = {})
+ @name = name
+ @price = price
+
+ if promotion_hash.has_key?(:get_one_free)
+ @promotion = OneProductFreePromotion.new(promotion_hash[:get_one_free])
+ end
+
+ if promotion_hash.has_key?(:package)
+ @promotion = PackagePromotion.new(promotion_hash[:package])
+ end
+
+ if promotion_hash.has_key?(:threshold)
+ @promotion = ThresholdPromotion.new(promotion_hash[:threshold])
+ end
+ end
+
+ def get_price_before_promotions(quantity)
+ @price * quantity
+ end
+
+ def get_price(quantity)
+ result = @price * quantity
+ result = result - @promotion.get_discount(self, quantity) if @promotion
+ result
+ end
+end
+
+class OneProductFreePromotion
+ attr_accessor :free_product_index
+
+ def initialize(free_product_index)
+ @free_product_index = free_product_index
+ end
+
+ def get_discount(product, quantity)
+ (quantity / @free_product_index) * product.price
+ end
+end
+
+class PackagePromotion
+ attr_accessor :package_size, :discount
+
+ def initialize(promotion_info)
+ @package_size = promotion_info.keys[0]
+ @discount = promotion_info[@package_size]
+ end
+
+ def get_discount(product, quantity)
+ products_with_discount = quantity - (quantity % @package_size)
+ products_with_discount * product.price * @discount / 100
+ end
+end
+
+class ThresholdPromotion
+ attr_accessor :threshold, :discount
+
+ def initialize(promotion_info)
+ @threshold = promotion_info.keys[0]
+ @discount = promotion_info[@threshold]
+ end
+
+ def get_discount(product, quantity)
+ if quantity < @threshold
+ return BigDecimal.new("0")
+ end
+
+ products_with_discount = quantity - @threshold
+ products_with_discount * product.price * @discount / 100
+ end
+end
+
+class Inventory
+ attr_accessor :products, :coupons
+
+ def initialize
+ @products = {}
+ @coupons = {}
+ end
+
+ def register(name, price, promotion = {})
+ if name.length > 40
+ raise "Invalid parameters passed."
+ end
+
+ product_price = BigDecimal(price)
+ if product_price < 0.01 or product_price > 999.99
+ raise "Invalid parameters passed."
+ end
+
+ product = Product.new(name, product_price, promotion)
+ @products[name] = product
+ end
+
+ def register_coupon(name, coupon_info)
+ if coupon_info.has_key?(:percent)
+ coupon = PercentCoupon.new(coupon_info[:percent])
+ end
+
+ if coupon_info.has_key?(:amount)
+ coupon = AmountCoupon.new(coupon_info[:amount])
+ end
+
+ coupons[name] = coupon
+ end
+
+ def get_product(name)
+ @products[name]
+ end
+
+ def new_cart
+ Cart.new(self)
+ end
+
+end
+
+class PercentCoupon
+ attr_accessor :percent
+
+ def initialize(percent)
+ @percent = percent
+ end
+
+ def get_discount(cart_total)
+ cart_total * @percent / 100
+ end
+end
+
+class AmountCoupon
+ attr_accessor :amount
+
+ def initialize(amount)
+ @amount = BigDecimal.new(amount)
+ end
+
+ def get_discount(cart_total)
+ if cart_total < @amount
+ return cart_total
+ end
+
+ @amount
+ end
+end
+
+class Cart
+ attr_accessor :inventory, :quantities, :coupon_name
+
+ def initialize(inventory)
+ @inventory = inventory
+ @quantities = {}
+ end
+
+ def add(product_name, quantity = 1)
+ if quantity < 1
+ raise "Invalid parameters passed."
+ end
+
+ if not @inventory.get_product(product_name)
+ raise "Trying to add in the cart a product that doesn't exist!"
+ end
+
+ new_quantity = @quantities.fetch(product_name, 0) + quantity
+ if new_quantity > 99
+ raise "Quantity should not exceed 99"
+ end
+
+ @quantities[product_name] = new_quantity
+ end
+
+ def products_sum
+ result = @quantities.inject(BigDecimal.new("0")) do |sum, pair|
+ product = @inventory.get_product(pair[0])
+ sum + product.get_price(pair[1])
+ end
+ end
+
+ def total
+ result = products_sum()
+
+ if @coupon_name
+ result = result - coupon.get_discount(result)
+ end
+
+ result
+ end
+
+ def invoice
+ InvoiceWriter.new.invoice(self)
+ end
+
+ def use(coupon_name)
+ if not inventory.coupons.has_key?(coupon_name)
+ raise "Trying to use a coupon that doesn't exist"
+ end
+
+ @coupon_name = coupon_name
+ end
+
+ def coupon()
+ inventory.coupons[@coupon_name]
+ end
+
+end
+
+class InvoiceWriter
+
+ DelimiterLine = "+------------------------------------------------+----------+\n"
+ TableHeadersLine = "| Name qty | price |\n"
+ ProductLine = "| %s %s | %s |\n"
+ PromotionLine = "| %s| %s |\n"
+ CouponDescription = "Coupon %s - %s off"
+ CouponLine = "| %s | %s |\n"
+ TotalLine = "| TOTAL | %s |\n"
+
+ Suffixes = Hash.new("th").merge({1 => "st", 2 => "nd", 3 => "rd"})
+
+ def invoice(cart)
+ result = DelimiterLine + TableHeadersLine + DelimiterLine
+
+ cart.quantities.each do |product_name, quantity|
+ product = cart.inventory.get_product(product_name)
+ result << get_product_line(product, quantity)
+ result << get_promotion_line(product, quantity) if product.promotion
+ end
+
+ result << get_coupon_line(cart) if cart.coupon
+ result << get_total_section(cart)
+ end
+
+ def get_product_line(product, quantity)
+ formatted_name = product.name.ljust(42)
+ formatted_quantity = quantity.to_s.rjust(3)
+ price = product.price * quantity
+ formatted_price = format_price(price)
+
+ ProductLine % [formatted_name, formatted_quantity, formatted_price]
+ end
+
+ def get_promotion_line(product, quantity)
+ promotion = product.promotion
+ formatted_name = get_promotion_text(promotion).ljust(45)
+ price = -promotion.get_discount(product, quantity)
+ formatted_price = format_price(price)
+ PromotionLine % [formatted_name, formatted_price]
+ end
+
+ def get_promotion_text(promotion)
+ if promotion.kind_of?(OneProductFreePromotion)
+ return "(buy %s, get 1 free)" % (promotion.free_product_index - 1)
+ end
+
+ if promotion.kind_of?(PackagePromotion)
+ return "(get %s off for every %d)" %
+ [promotion.discount.to_s + "%", promotion.package_size]
+ end
+
+ if promotion.kind_of?(ThresholdPromotion)
+ return "(%s off of every after the %s)" %
+ [promotion.discount.to_s + "%", get_formatted_threshold(promotion.threshold)]
+ end
+ end
+
+ def get_formatted_threshold(threshold)
+ threshold.to_s + Suffixes[threshold]
+ end
+
+ def get_coupon_line(cart)
+ formatted_name = get_coupon_text(cart.coupon_name, cart.coupon).ljust(46)
+ price = -cart.coupon.get_discount(cart.products_sum)
+ formatted_price = format_price(price)
+ CouponLine % [formatted_name, formatted_price]
+ end
+
+ def get_coupon_text(coupon_name, coupon)
+ if coupon.kind_of?(PercentCoupon)
+ return CouponDescription % [coupon_name, coupon.percent.to_s + "%"]
+ end
+ if coupon.kind_of?(AmountCoupon)
+ return CouponDescription % [coupon_name, "%.2f" % coupon.amount]
+ end
+ end
+
+ def get_total_section(cart)
+ result = ""
+ result << DelimiterLine
+ result << TotalLine % format_price(cart.total)
+ result << DelimiterLine
+ end
+
+ def format_price(price)
+ ("%5.2f" % (price.to_f.round(2))).rjust(8)
+ end
+end