/
cart_service.rb
171 lines (133 loc) · 4.94 KB
/
cart_service.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# frozen_string_literal: true
require 'open_food_network/scope_variant_to_hub'
# Previously Spree::OrderPopulator. Modified to work with max_quantity and variant overrides.
class CartService
attr_accessor :order
attr_reader :errors
def initialize(order)
@order = order
@errors = ActiveModel::Errors.new(self)
end
def populate(from_hash)
@distributor, @order_cycle = distributor_and_order_cycle
variants_data = read_variants_hash(from_hash)
@order.with_lock do
attempt_cart_add_variants variants_data
overwrite_variants variants_data
end
valid?
end
def valid?
errors.empty?
end
private
def attempt_cart_add_variants(variants_data)
loaded_variants = indexed_variants(variants_data)
variants_data.each do |variant_data|
loaded_variant = loaded_variants[variant_data[:variant_id]]
if loaded_variant.deleted? || !variant_data[:quantity].positive?
cart_remove(loaded_variant)
next
end
next unless varies_from_cart(variant_data, loaded_variant)
attempt_cart_add(loaded_variant, variant_data[:quantity], variant_data[:max_quantity])
end
end
def indexed_variants(variants_data)
@indexed_variants ||= begin
variant_ids_in_data = variants_data.map{ |v| v[:variant_id] }
Spree::Variant.with_deleted.where(id: variant_ids_in_data).
includes(:default_price, :stock_items, :product).
index_by(&:id)
end
end
def attempt_cart_add(variant, quantity, max_quantity = nil)
scoper.scope(variant)
return unless valid_variant?(variant)
cart_add(variant, quantity, max_quantity)
end
def cart_add(variant, quantity, max_quantity)
attributes = final_quantities(variant, quantity, max_quantity)
if attributes[:quantity].positive?
@order.contents.update_or_create(variant, attributes)
else
cart_remove(variant)
end
end
def cart_remove(variant)
order.contents.remove(variant)
rescue ActiveRecord::RecordNotFound
# Nothing to remove; no line items for this variant were found.
end
def final_quantities(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
final_quantity = [quantity, on_hand].min
final_max_quantity = max_quantity # max_quantity is not capped
{ quantity: final_quantity, max_quantity: final_max_quantity }
end
def overwrite_variants(variants)
variants_removed(variants).each do |variant_id|
variant = Spree::Variant.with_deleted.find(variant_id)
cart_remove(variant)
end
end
def scoper
@scoper ||= OpenFoodNetwork::ScopeVariantToHub.new(@distributor)
end
def read_variants_hash(data)
variants_array = []
(data[:variants] || []).each do |variant_id, quantity|
if quantity.is_a?(ActionController::Parameters)
variants_array.push({
variant_id: variant_id.to_i,
quantity: quantity[:quantity].to_i,
max_quantity: quantity[:max_quantity].to_i
})
else
variants_array.push({
variant_id: variant_id.to_i,
quantity: quantity.to_i
})
end
end
variants_array
end
def distributor_and_order_cycle
[@order.distributor, @order.order_cycle]
end
# Returns true if the saved cart differs from what's in the posted data, otherwise false
def varies_from_cart(variant_data, loaded_variant)
li = line_item_for_variant loaded_variant
li_added = li.nil? && (variant_data[:quantity].to_i > 0 || variant_data[:max_quantity].to_i > 0)
li_quantity_changed = li.present? && li.quantity != 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, I18n.t(:spree_order_cycle_error)) unless order_cycle_provided
order_cycle_provided
end
def check_variant_available_under_distribution(variant)
return true if OrderCycles::DistributedVariantsService.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(variant)
order.find_line_item_by_variant variant
end
def variant_ids_in_cart
@order.line_items.pluck :variant_id
end
end