iT邦幫忙

2021 iThome 鐵人賽

0
Modern Web

初階 Rails 工程師的養成系列 第 32

Day32. 使用Decorator Pattern 實作攤提

當我們要做開立發票、發票折讓的時候,或者對第三方如 POS 整合系統要同步資料時,可能會遇到需要使用『攤提』的情境。在開始介紹攤提以前,我們先介紹基本情境,以及各個資料表之間的關係

#======= 訂單 Order
columns:
  used_rebate: 紀錄使用的回饋點
  used_birth: 紀錄使用的生日點
  target_price: 滿額贈實際扣點
 
#======= 賣出商品 OrderItem
columns:
  quantity: 賣出數量
  price: 折價後價錢
  
#======= 退貨商品 ReturnOrderItem
columns:
  quantity: 退貨數量
  

#======= 訂單與賣出商品之間的關係
order has_many order_items
order_item belongs_to order

#======= 賣出商品、退貨商品之間的關係
order_item has_one return_order_item
return_order_item belongs_to order_item

decorator pattern

decorator pattern 的精神是,在不動用該實體的行為的同時,對該實體新增一些顯示方法。以該情境來說,我們不動訂單、退貨單的金額,用現有的方法算攤提後的價格。

實作

下列為將計算退款的Decorator寫在module,並透過 module#included 在被include之後對Order, OrderItem 加入實體方法

module OrderSyncDecorator
  def self.included(base)
    Order.class_eval do
      # 減項: 使用點數、滿額贈
      define_method :minus, -> { used_rebate.to_i + target_price.to_i }
    end

    OrderItem.class_eval do
      # 退貨完後的數量
      define_method :qty_after_return, -> { quantity.to_i - return_order_item&.quantity.to_i }
      # 比率分子
      define_method :item_numerator, -> { qty_after_return * price }
    end

    Order.class_eval do
      # 比率分母
      define_method :item_denominator, -> { order_items.sum(&:item_numerator) }
    end

    OrderItem.class_eval do
      # 比率
      define_method :item_ratio, -> { item_numerator.to_f / order.item_denominator.to_f }
    end

    Order.class_eval do
      # 攤平金額
      define_method :flatten_minus, -> { order_items.map { |_| {id: _.id, price: (_.item_ratio * minus.to_f).to_i} } }
      # 誤差數
      define_method :deviation_minus, -> { minus - flatten_minus.sum { |_| _[:price] } }
      # 修正金額 (+在首位)
      define_method :corr_flatten_minus, -> { e = flatten_minus; [e[0].merge(price: e[0][:price] + deviation_minus), *e[1..-1]] }
    end

    OrderItem.class_eval do
      # 商品小計
      define_method :subtotal, -> { quantity * price - minus }
      # minus
      define_method :minus, -> { order.corr_flatten_minus.find { |_| _[:id] == id }.try(:[], :price) }
      end
      # 單項折讓
      define_method :rebate, -> { variant.price - subtotal.to_f }
      # 單項折扣
      define_method :discount, -> {  100 * (subtotal.to_f / variant.price.to_f) }
    end
  end
end

以上例來說,攤提的算法為

  • 訂單Order ➡️ 先將訂單的減項算出來

  • 訂單項OrderItem ➡️ 將退貨後的數量算出來

  • 訂單項OrderItem ➡️ 將攤提比率的分子算出來

  • 訂單Order ➡️ 先將訂單攤提比率的分母算出來

  • 訂單項OrderItem ➡️ 將攤提比率算出來

  • 訂單Order

    ➡️ 將攤提金額算出來flatten_minus

    ➡️ 將誤差算出來deviation_minus

    ➡️ 修正誤差corr_flatten_minus :將誤差修正的結果

  • 訂單項OrderItem

    ➡️ 算商品小計

    ➡️ 算用比率 / 修正 後的減項 ? 從corr_flatten_minus 取得結果

    ➡️ 算單項折讓

    ➡️ 算單項折扣

結論

搭配之前介紹的繼承,只要有分銷相關的邏輯,包含後台畫面、發票折讓、同步訂單等,只要繼承了OrderSyncDecorator,就可以共用包含在OrderSyncDecorator裡面所有的實體方法。

參考資料


上一篇
Day31. Rails 搜尋的強大幫手 - Ransack
下一篇
Day33. 使用RSpec寫測試
系列文
初階 Rails 工程師的養成34

尚未有邦友留言

立即登入留言