Ростислав обнови решението на 05.11.2011 21:24 (преди около 13 години)
+require 'bigdecimal'
+require 'bigdecimal/util'
+
+module InvoiceGenerator
+
+ def gen_line(name, price, quantity = nil)
+ if quantity
+ "| %-40s %5d | %8.2f |\n" % [name, quantity, price]
+ else
+ "| %-46s | %8.2f |\n" % [name, price]
+ end
+ end
+
+ def border_row
+ "+------------------------------------------------+----------+\n"
+ end
+
+ def invoice_header
+ border_row <<
+ "| Name qty | price |\n" <<
+ border_row
+ end
+
+ def invoice_footer(total)
+ border_row <<
+ gen_line("TOTAL", total) <<
+ border_row
+ end
+end
+
+class Promotion_get_one_free
+ include InvoiceGenerator
+
+ def initialize(commodity, nth_free)
+ @commodity = commodity
+ @nth_free = nth_free
+ end
+
+ def discount(qty)
+ -1 * @commodity.price * (qty / @nth_free)
+ end
+
+ def to_invoice(qty)
+ gen_line " (buy #{@nth_free-1}, get 1 free)", discount(qty)
+ end
+end
+
+class Promotion_package
+ include InvoiceGenerator
+
+ def initialize(commodity, package)
+ @commodity = commodity
+ flat_pack = package.flatten
+ @quantity = flat_pack[0]
+ @percentage = flat_pack[1]
+ end
+
+ def discount(qty)
+ -1 * (qty / @quantity) * @quantity * (@percentage / '100'.to_d) * @commodity.price
+ end
+
+ def to_invoice(qty)
+ gen_line " (get #{@percentage}% off for every #{@quantity})", discount(qty)
+ end
+end
+
+class Promotion_threshold
+ include InvoiceGenerator
+
+ def initialize(commodity, threshold)
+ @commodity = commodity
+ flat_threshold = threshold.flatten
+ @quantity = flat_threshold[0]
+ @percentage = flat_threshold[1]
+ end
+
+ def discount(qty)
+ if qty > @quantity
+ -1 * (qty - @quantity) * (@percentage / '100'.to_d) * @commodity.price
+ else
+ 0
+ end
+ end
+
+ def to_invoice(qty)
+ ords = ["th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th"] * 10
+ ords[11] = ords[12] = ords[13] = "th"
+ ordinized = "#{@quantity}#{ords[@quantity % 100]}"
+ gen_line " (#{@percentage}% off of every after the #{ordinized})", discount(qty)
+ end
+end
+
+class Coupon_percent
+ include InvoiceGenerator
+
+ def initialize(name, percent)
+ @name = name
+ @percent = percent
+ end
+
+ def discount(price)
+ -1 * price * (@percent / '100'.to_d)
+ end
+
+ def to_invoice(price)
+ gen_line "Coupon #{@name} - #{@percent}% off", discount(price)
+ end
+end
+
+class Coupon_amount
+ include InvoiceGenerator
+
+ def initialize(name, amount)
+ @name = name
+ @amount = amount.to_s.to_d
+ end
+
+ def discount(price)
+ price - @amount > 0.00 ? -@amount : -price
+ end
+
+ def to_invoice(price)
+ gen_line "Coupon %s - %.2f off" % [@name, @amount], discount(price)
+ end
+end
+
+class Commodity
+ include InvoiceGenerator
+
+ attr_reader :name, :price
+
+ def initialize(name, price, promo_hash = {})
+ raise "Name too long!" if name.length > 40
+ raise "Price out of bounds!" if price.to_d < 0.01 or price.to_d > 999.99
+ @name = name
+ @price = price.to_d
+ promo_arr = promo_hash.flatten
+ unless promo_arr == []
+ class_name = "Promotion_" + promo_arr[0].to_s
+ @promotion = Object.const_get(class_name).new(self, promo_arr[1])
+ end
+ end
+
+ def eql?(other)
+ other.name == name
+ end
+
+ def price_for(quantity)
+ result = @price * quantity
+ result += @promotion.discount(quantity) if @promotion
+ result
+ end
+
+ def to_invoice(quantity)
+ result = gen_line @name, @price * quantity, quantity
+ result << @promotion.to_invoice(quantity) if @promotion
+ result
+ end
+end
+
+class Cart
+ include InvoiceGenerator
+
+ def initialize(inventory)
+ @inventory = inventory
+ @coupon = nil
+ @commodities = Hash.new(0)
+ end
+
+ def add(com_name, quantity = 1)
+ commodity = @inventory.get_commodity com_name
+ raise "Unknown commodity \"#{com_name}\"" unless commodity
+
+ qty = @commodities[commodity] + quantity
+ raise "Bad quantity in shopping cart!" if qty <= 0 or qty > 99
+ @commodities[commodity] = qty
+ end
+
+ def use(coupon_name)
+ @coupon = @inventory.get_coupon coupon_name
+ end
+
+ def total
+ result = '0'.to_d
+ @commodities.each_pair { |com, qty| result += com.price_for qty }
+ result += @coupon.discount result if @coupon
+ result
+ end
+
+ def invoice
+ result = invoice_header
+ tot = '0'.to_d
+ @commodities.each_pair do |com, qty|
+ result << com.to_invoice(qty)
+ tot += com.price_for qty
+ end
+ result << @coupon.to_invoice(tot) if @coupon
+ result << invoice_footer(total)
+ result
+ end
+end
+
+class Inventory
+ def initialize
+ @commodities = {}
+ @coupons = {}
+ end
+
+ def register(name, *args)
+ raise "Commodity already registered!" if @commodities.key? name
+ @commodities[name] = Commodity.new name, *args
+ end
+
+ def register_coupon(name, type)
+ type_arr = type.flatten
+ class_name = "Coupon_" + type_arr[0].to_s
+ @coupons[name] = Object.const_get(class_name).new(name, type_arr[1])
+ end
+
+ def new_cart
+ Cart.new self
+ end
+
+ def get_commodity(name)
+ @commodities[name]
+ end
+
+ def get_coupon(name)
+ @coupons[name]
+ end
+end