What's your biggest current challenge? Have your say in Community Polls along the right column.

Multibuy Script not working - InstructionQuotaExceeded

Multibuy Script not working - InstructionQuotaExceeded

JamesB2000
Tourist
8 0 7

I have a Shopify Script that applies a special pricing rule. For instance, if a customer orders 3 units of a device tagged as "3 For 10", the total price for those items will be £20. This rule is designed to work dynamically, so if they add 6 units to the cart, the total price would adjust to £20 accordingly.

 

However, there is an issue when the customer adds 9 different flavors of items, each tagged as "3 For 10". In this case, the discount doesn't apply at all, and an error message "InstructionQuotaExceeded" is encountered. I'm seeking assistance to fix this problem.

 

Please find the script below:

 

BUY_X_GET_Y_FOR_Z = [
  {
    product_selector_match_type: :include,
    product_selector_type: :tag,
    product_selectors: ["2 For 10"],
    quantity_to_buy: 2,
    final_price: 10,
  },
  {
    product_selector_match_type: :include,
    product_selector_type: :tag,
    product_selectors: ["4 For 10"],
    quantity_to_buy: 4,
    final_price: 10,
  },
  {
    product_selector_match_type: :include,
    product_selector_type: :tag,
    product_selectors: ["5 For 20"],
    quantity_to_buy: 5,
    final_price: 20,
  },  
  {
    product_selector_match_type: :include,
    product_selector_type: :tag,
    product_selectors: ["5 For 50"],
    quantity_to_buy: 5,
    final_price: 50,
  },  
  {
    product_selector_match_type: :include,
    product_selector_type: :tag,
    product_selectors: ["3 For 60"],
    quantity_to_buy: 3,
    final_price: 60,
  },  
  {
    product_selector_match_type: :include,
    product_selector_type: :tag,
    product_selectors: ["3 For 15"],
    quantity_to_buy: 3,
    final_price: 15,
  },  
  {
    product_selector_match_type: :include,
    product_selector_type: :tag,
    product_selectors: ["3 For 10"],
    quantity_to_buy: 3,
    final_price: 10,
  },
  {
    product_selector_match_type: :include,
    product_selector_type: :tag,
    product_selectors: ["3 For 12"],
    quantity_to_buy: 3,
    final_price: 12,
  },
]
class ProductSelector
  def initialize(match_type, selector_type, selectors)
    @match_type = match_type
    @comparator = match_type == :include ? 'any?' : 'none?'
    @Selector_type = selector_type
    @selectors = selectors
  end

  def match?(line_item)
    if self.respond_to?(@selector_type)
      self.send(@selector_type, line_item)
    else
      raise RuntimeError.new('Invalid product selector type')
    end
  end

  def tag(line_item)
    product_tags = line_item.variant.product.tags.map { |tag| tag.downcase.strip }
    @selectors = @selectors.map { |selector| selector.downcase.strip }
    (@selectors & product_tags).send(@comparator)
  end

  def type(line_item)
    @selectors = @selectors.map { |selector| selector.downcase.strip }
    (@match_type == :include) == @selectors.include?(line_item.variant.product.product_type.downcase.strip)
  end

  def vendor(line_item)
    @selectors = @selectors.map { |selector| selector.downcase.strip }
    (@match_type == :include) == @selectors.include?(line_item.variant.product.vendor.downcase.strip)
  end

  def product_id(line_item)
    (@match_type == :include) == @selectors.include?(line_item.variant.product.id)
  end

  def variant_id(line_item)
    (@match_type == :include) == @selectors.include?(line_item.variant.id)
  end

  def all(line_item)
    true
  end
end

# ================================================================
# DollarDiscountApplicator
#
# Applies the entered discount to the supplied line item.
# ================================================================
class DollarDiscountApplicator
  def initialize(discount_message)
    @discount_message = discount_message
  end

  def apply(line_item, discount_amount)
    new_line_price = line_item.line_price - discount_amount
    line_item.change_line_price(new_line_price, message: @discount_message)
  end
end

# ================================================================
# BuyXOfYForZCampaign
#
# Buy a certain number of matching items for a specific price.
# ================================================================
class BuyXOfYForZCampaign
  def initialize(campaigns)
    @campaigns = campaigns
  end

  def run(cart)
    @campaigns.each do |campaign|
      product_selector = ProductSelector.new(
        campaign[:product_selector_match_type],
        campaign[:product_selector_type],
        campaign[:product_selectors],
      )

      eligible_items = cart.line_items.select { |line_item| product_selector.match?(line_item) }

      next if eligible_items.nil?

      eligible_item_count = eligible_items.map(&:quantity).reduce(0, :+)
      quantity_to_buy = campaign[:quantity_to_buy]
      number_of_offers = (eligible_item_count / quantity_to_buy).floor

      next unless number_of_offers > 0

      number_of_discountable_items = number_of_offers * quantity_to_buy
      total_offer_price = Money.new(cents: 100) * (number_of_offers * campaign[:final_price])
      discount_applicator = DollarDiscountApplicator.new(campaign[:discount_message])

      self.loop_items(cart, eligible_items, number_of_discountable_items, total_offer_price, discount_applicator)
    end
  end

  def loop_items(cart, line_items, num_to_discount, total_price, discount_applicator)
    current_price = Money.zero
    avg_price = total_price * (1 / num_to_discount)

    line_items = line_items.sort_by { |line_item| line_item.variant.price }

    line_items.each do |line_item|
      break if num_to_discount <= 0

      if line_item.quantity > num_to_discount
        split_line_item = line_item.split(take: num_to_discount)
        discount_amount = split_line_item.line_price - (total_price - current_price)
        discount_applicator.apply(split_line_item, discount_amount)
        position = cart.line_items.find_index(line_item)
        cart.line_items.insert(position + 1, split_line_item)
        break
      elsif line_item.quantity == num_to_discount
        discount_amount = line_item.line_price - (total_price - current_price)
        discount_applicator.apply(line_item, discount_amount)
        break
      else
        if line_item.variant.price <= avg_price
          current_price += line_item.line_price
        else
          discount_amount = (line_item.variant.price - avg_price) * line_item.quantity
          current_price += (line_item.line_price - discount_amount)
          discount_applicator.apply(line_item, discount_amount)
        end

        num_to_discount -= line_item.quantity
      end
    end
  end
end

CAMPAIGNS = [
  BuyXOfYForZCampaign.new(BUY_X_GET_Y_FOR_Z),
]

CAMPAIGNS.each do |campaign|
  campaign.run(Input.cart)
end

Output.cart = Input.cart

 

Replies 0 (0)