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里面所有的实体方法。

参考资料


<<:  [Java Day21] 5.1. 私有化

>>:  [DAY 17]Discord server串接webhook

JWT 验证魔术

本来今天要介绍的是验证授权,但官方那一套我不是很喜欢,所以就从网路上找其他的解决办法,果不其然就找到...

DAY 11 Quick replies & Action objects in Messaging API

在写这篇时发现,因为在上篇文章有提到模板讯息,里面有大量出现Action objects,Quick...

Vuex 的使用偏好

这是我个人的使用偏好,而且是以抽象资料型别的使用方式来理解 vuex 的使用方式。也许,我是说也许...

【Day 25】React 与 Immutible

Immutable Immutable 中文意思为不可变的, 即重新赋值後, 新的值和原始的值并不互...

Day18. Slim & Pug - 缩排式的 html

由於可能很多人会不习惯缩排的写法来写html,然後在Day17以後的章节,汉汉老师会大量的使用缩排式...