require 'open_food_network/scope_variant_to_hub' class CartService attr_accessor :order, :currency attr_reader :variants_h attr_reader :errors def initialize(order) @order = order @currency = order.currency @errors = ActiveModel::Errors.new(self) end def populate(from_hash, overwrite = false) @distributor, @order_cycle = distributor_and_order_cycle @order.with_lock do variants = read_variants from_hash attempt_cart_add_variants variants overwrite_variants variants unless !overwrite end valid? end def attempt_cart_add_variants(variants) variants.each do |v| if varies_from_cart(v) attempt_cart_add(v[:variant_id], v[:quantity], v[:max_quantity]) end end end def attempt_cart_add(variant_id, quantity, max_quantity = nil) quantity = quantity.to_i max_quantity = max_quantity.to_i if max_quantity variant = Spree::Variant.find(variant_id) OpenFoodNetwork::ScopeVariantToHub.new(@distributor).scope(variant) return unless quantity > 0 && valid_variant?(variant) cart_add(variant, quantity, max_quantity) end def cart_add(variant, quantity, max_quantity) quantity_to_add, max_quantity_to_add = quantities_to_add(variant, quantity, max_quantity) if quantity_to_add > 0 @order.add_variant(variant, quantity_to_add, max_quantity_to_add, currency) else @order.remove_variant variant end end def quantities_to_add(variant, quantity, max_quantity) # If not enough stock is available, add as much as we can to the cart on_hand = variant.on_hand on_hand = [quantity, max_quantity].compact.max if variant.on_demand quantity_to_add = [quantity, on_hand].min max_quantity_to_add = max_quantity # max_quantity is not capped [quantity_to_add, max_quantity_to_add] end def overwrite_variants(variants) variants_removed(variants).each do |id| cart_remove(id) end end private def read_variants(data) @variants_h = read_products_hash(data) + read_variants_hash(data) end def read_products_hash(data) # This is most probably dead code, this bugsnag notification will confirm it notify_bugsnag(data) if data[:products].present? (data[:products] || []).map do |_product_id, variant_id| { variant_id: variant_id, quantity: data[:quantity] } end end def notify_bugsnag(data) Bugsnag.notify(RuntimeError.new("CartService.populate called with products hash"), data: data.as_json) end def read_variants_hash(data) (data[:variants] || []).map do |variant_id, quantity| if quantity.is_a?(Hash) { variant_id: variant_id, quantity: quantity[:quantity], max_quantity: quantity[:max_quantity] } else { variant_id: variant_id, quantity: quantity } end end end def valid? errors.empty? end def cart_remove(variant_id) variant = Spree::Variant.find(variant_id) @order.remove_variant(variant) end def distributor_and_order_cycle [@order.distributor, @order.order_cycle] end def varies_from_cart(variant_data) li = line_item_for_variant_id variant_data[:variant_id] li_added = li.nil? && (variant_data[:quantity].to_i > 0 || variant_data[:max_quantity].to_i > 0) li_quantity_changed = li.present? && li.quantity.to_i != variant_data[:quantity].to_i li_max_quantity_changed = li.present? && li.max_quantity.to_i != variant_data[:max_quantity].to_i li_added || li_quantity_changed || li_max_quantity_changed end def variants_removed(variants_data) variant_ids_given = variants_data.map { |data| data[:variant_id].to_i } (variant_ids_in_cart - variant_ids_given).uniq end def valid_variant?(variant) check_order_cycle_provided && check_variant_available_under_distribution(variant) end def check_order_cycle_provided order_cycle_provided = @order_cycle.present? errors.add(:base, "Please choose an order cycle for this order.") unless order_cycle_provided order_cycle_provided end def check_variant_available_under_distribution(variant) return true if OrderCycleDistributedVariants.new(@order_cycle, @distributor).available_variants.include? variant errors.add(:base, I18n.t(:spree_order_populator_availability_error)) false end def line_item_for_variant_id(variant_id) order.find_line_item_by_variant Spree::Variant.find(variant_id) end def variant_ids_in_cart @order.line_items.pluck :variant_id end end