flowcommerce_spree 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +91 -0
- data/Rakefile +33 -0
- data/SPREE_FLOW.md +134 -0
- data/app/assets/javascripts/flowcommerce_spree/application.js +13 -0
- data/app/assets/stylesheets/flowcommerce_spree/application.css +15 -0
- data/app/controllers/concerns/current_zone_loader_decorator.rb +49 -0
- data/app/controllers/flowcommerce_spree/webhooks_controller.rb +25 -0
- data/app/helpers/flowcommerce_spree/application_helper.rb +6 -0
- data/app/helpers/spree/admin/orders_helper_decorator.rb +17 -0
- data/app/helpers/spree/core/controller_helpers/flow_io_order_helper_decorator.rb +53 -0
- data/app/mailers/spree/spree_order_mailer_decorator.rb +24 -0
- data/app/models/flowcommerce_spree/settings.rb +8 -0
- data/app/models/spree/credit_card_decorator.rb +9 -0
- data/app/models/spree/flow_io_product_decorator.rb +91 -0
- data/app/models/spree/flow_io_variant_decorator.rb +205 -0
- data/app/models/spree/gateway/spree_flow_gateway.rb +116 -0
- data/app/models/spree/line_item_decorator.rb +15 -0
- data/app/models/spree/order_decorator.rb +179 -0
- data/app/models/spree/promotion_decorator.rb +10 -0
- data/app/models/spree/promotion_handler/coupon_decorator.rb +30 -0
- data/app/models/spree/spree_user_decorator.rb +15 -0
- data/app/models/spree/taxon_decorator.rb +37 -0
- data/app/models/spree/zone_decorator.rb +7 -0
- data/app/models/spree/zones/flow_io_product_zone_decorator.rb +55 -0
- data/app/services/flowcommerce_spree/import_experience_items.rb +76 -0
- data/app/services/flowcommerce_spree/import_experiences.rb +37 -0
- data/app/services/flowcommerce_spree/order_sync.rb +231 -0
- data/app/views/layouts/flowcommerce_spree/application.html.erb +14 -0
- data/app/views/spree/admin/payments/index.html.erb +28 -0
- data/app/views/spree/admin/promotions/edit.html.erb +57 -0
- data/app/views/spree/admin/shared/_order_summary.html.erb +44 -0
- data/app/views/spree/admin/shared/_order_summary_flow.html.erb +13 -0
- data/app/views/spree/order_mailer/confirm_email.html.erb +86 -0
- data/app/views/spree/order_mailer/confirm_email.text.erb +38 -0
- data/config/initializers/flowcommerce_spree.rb +7 -0
- data/config/routes.rb +5 -0
- data/db/migrate/20201021160159_add_type_and_meta_to_spree_zone.rb +23 -0
- data/db/migrate/20201021755957_add_meta_to_spree_tables.rb +17 -0
- data/db/migrate/20201022173210_add_zone_type_to_spree_zone_members.rb +24 -0
- data/db/migrate/20201022174252_add_kind_to_zone.rb +22 -0
- data/lib/flow/error.rb +73 -0
- data/lib/flow/pay_pal.rb +25 -0
- data/lib/flow/simple_gateway.rb +115 -0
- data/lib/flowcommerce_spree.rb +31 -0
- data/lib/flowcommerce_spree/api.rb +48 -0
- data/lib/flowcommerce_spree/engine.rb +27 -0
- data/lib/flowcommerce_spree/experience_service.rb +65 -0
- data/lib/flowcommerce_spree/logging_http_client.rb +43 -0
- data/lib/flowcommerce_spree/logging_http_handler.rb +15 -0
- data/lib/flowcommerce_spree/refresher.rb +81 -0
- data/lib/flowcommerce_spree/session.rb +71 -0
- data/lib/flowcommerce_spree/version.rb +5 -0
- data/lib/flowcommerce_spree/webhook_service.rb +98 -0
- data/lib/simple_csv_writer.rb +44 -0
- data/lib/tasks/flowcommerce_spree.rake +289 -0
- metadata +220 -0
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Flow (2017)
|
4
|
+
# Enable this modifications if you want to display flow localized line item
|
5
|
+
# Example: https://i.imgur.com/7v2ix2G.png
|
6
|
+
module Spree
|
7
|
+
LineItem.class_eval do
|
8
|
+
# admin show line item price
|
9
|
+
def single_money
|
10
|
+
price = display_price.to_s
|
11
|
+
price += " (#{order.flow_line_item_price(self)})" if order.flow_order
|
12
|
+
price
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# `:display_total` modifications to display total prices beside Spree default. Example: https://i.imgur.com/7v2ix2G.png
|
4
|
+
module Spree # rubocop:disable Metrics/ModuleLength
|
5
|
+
# Added flow specific methods to Spree::Order
|
6
|
+
Order.class_eval do
|
7
|
+
serialize :meta, ActiveRecord::Coders::JSON.new(symbolize_keys: true)
|
8
|
+
|
9
|
+
store_accessor :meta, :flow_data
|
10
|
+
|
11
|
+
before_save :sync_to_flow_io
|
12
|
+
after_touch :sync_to_flow_io
|
13
|
+
|
14
|
+
def sync_to_flow_io
|
15
|
+
return unless zone&.flow_io_active_experience? && state == 'cart' && line_items.size > 0
|
16
|
+
|
17
|
+
flow_io_order = FlowcommerceSpree::OrderSync.new(order: self)
|
18
|
+
flow_io_order.build_flow_request
|
19
|
+
flow_io_order.synchronize! if flow_data['digest'] != flow_io_order.digest
|
20
|
+
end
|
21
|
+
|
22
|
+
def display_total
|
23
|
+
price = FlowcommerceSpree::Api.format_default_price total
|
24
|
+
price += " (#{flow_total})" if flow_order
|
25
|
+
price.html_safe
|
26
|
+
end
|
27
|
+
|
28
|
+
def flow_order
|
29
|
+
return unless flow_data&.[]('order')
|
30
|
+
|
31
|
+
Hashie::Mash.new flow_data['order']
|
32
|
+
end
|
33
|
+
|
34
|
+
# accepts line item, usually called from views
|
35
|
+
def flow_line_item_price(line_item, total = false)
|
36
|
+
result = if flow_order
|
37
|
+
id = line_item.variant.sku
|
38
|
+
|
39
|
+
lines = flow_order.lines || []
|
40
|
+
item = lines.find { |el| el['item_number'] == id }
|
41
|
+
|
42
|
+
return 'n/a' unless item
|
43
|
+
|
44
|
+
total ? item['total']['label'] : item['price']['label']
|
45
|
+
else
|
46
|
+
FlowcommerceSpree::Api.format_default_price(line_item.price * (total ? line_item.quantity : 1))
|
47
|
+
end
|
48
|
+
|
49
|
+
# add line item promo
|
50
|
+
# promo_total, adjustment_total
|
51
|
+
result += " (#{FlowcommerceSpree::Api.format_default_price(line_item.promo_total)})" if line_item.promo_total > 0
|
52
|
+
|
53
|
+
result
|
54
|
+
end
|
55
|
+
|
56
|
+
# prepares array of prices that can be easily renderd in templates
|
57
|
+
def flow_cart_breakdown
|
58
|
+
prices = []
|
59
|
+
|
60
|
+
price_model = Struct.new(:name, :label)
|
61
|
+
|
62
|
+
if flow_order
|
63
|
+
# duty, vat, ...
|
64
|
+
unless flow_order.prices
|
65
|
+
message = Flow::Error.format_order_message flow_order
|
66
|
+
raise Flow::Error, message
|
67
|
+
end
|
68
|
+
|
69
|
+
flow_order.prices.each do |price|
|
70
|
+
prices.push price_model.new(price['name'], price['label'])
|
71
|
+
end
|
72
|
+
else
|
73
|
+
price_elements =
|
74
|
+
%i[item_total adjustment_total included_tax_total additional_tax_total tax_total shipment_total promo_total]
|
75
|
+
price_elements.each do |el|
|
76
|
+
price = send(el)
|
77
|
+
if price > 0
|
78
|
+
label = FlowcommerceSpree::Api.format_default_price price
|
79
|
+
prices.push price_model.new(el.to_s.humanize.capitalize, label)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# discount is applied and we allways show it in default currency
|
84
|
+
if adjustment_total != 0
|
85
|
+
formated_discounted_price = FlowcommerceSpree::Api.format_default_price adjustment_total
|
86
|
+
prices.push price_model.new('Discount', formated_discounted_price)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# total
|
91
|
+
prices.push price_model.new(Spree.t(:total), flow_total)
|
92
|
+
|
93
|
+
prices
|
94
|
+
end
|
95
|
+
|
96
|
+
# shows localized total, if possible. if not, fall back to Spree default
|
97
|
+
def flow_total
|
98
|
+
# r flow_order.total.label
|
99
|
+
price = flow_order&.total&.label
|
100
|
+
price || FlowcommerceSpree::Api.format_default_price(total)
|
101
|
+
end
|
102
|
+
|
103
|
+
def flow_experience
|
104
|
+
model = Struct.new(:key)
|
105
|
+
model.new flow_order.experience.key
|
106
|
+
rescue StandardError => _e
|
107
|
+
model.new ENV.fetch('FLOW_BASE_COUNTRY')
|
108
|
+
end
|
109
|
+
|
110
|
+
def flow_io_experience_key
|
111
|
+
flow_data&.[]('exp')
|
112
|
+
end
|
113
|
+
|
114
|
+
def flow_io_experience_from_zone
|
115
|
+
self.flow_data = (flow_data || {}).merge!('exp' => zone.flow_io_experience)
|
116
|
+
end
|
117
|
+
|
118
|
+
def flow_io_order_id
|
119
|
+
flow_data&.dig('order', 'id')
|
120
|
+
end
|
121
|
+
|
122
|
+
def flow_io_attributes
|
123
|
+
flow_data&.dig('order', 'attributes') || {}
|
124
|
+
end
|
125
|
+
|
126
|
+
def add_user_consent_to_flow_data(consent, value)
|
127
|
+
self.flow_data['order'] ||= {}
|
128
|
+
self.flow_data['order']['attributes'] ||= {}
|
129
|
+
self.flow_data['order']['attributes'][consent] = value
|
130
|
+
end
|
131
|
+
|
132
|
+
def add_user_uuid_to_flow_data
|
133
|
+
self.flow_data['order'] ||= {}
|
134
|
+
self.flow_data['order']['attributes'] ||= {}
|
135
|
+
self.flow_data['order']['attributes']['user_uuid'] = user&.uuid
|
136
|
+
end
|
137
|
+
|
138
|
+
def flow_io_user_uuid
|
139
|
+
flow_data&.dig('order', 'attributes', 'user_uuid')
|
140
|
+
end
|
141
|
+
|
142
|
+
def checkout_url
|
143
|
+
"https://checkout.flow.io/#{FlowcommerceSpree::ORGANIZATION}/checkout/#{number}/" \
|
144
|
+
"contact-info?flow_session_id=#{flow_data['session_id']}"
|
145
|
+
end
|
146
|
+
|
147
|
+
# clear invalid zero amount payments. Solidus bug?
|
148
|
+
def clear_zero_amount_payments!
|
149
|
+
# class attribute that can be set to true
|
150
|
+
return unless Flow::Order.clear_zero_amount_payments
|
151
|
+
|
152
|
+
payments.where(amount: 0, state: %w[invalid processing pending]).map(&:destroy)
|
153
|
+
end
|
154
|
+
|
155
|
+
def flow_order_authorized?
|
156
|
+
flow_data&.[]('authorization') ? true : false
|
157
|
+
end
|
158
|
+
|
159
|
+
def flow_order_captured?
|
160
|
+
flow_data['capture'] ? true : false
|
161
|
+
end
|
162
|
+
|
163
|
+
# completes order and sets all states to finalized and complete
|
164
|
+
# used when we have confirmed capture from Flow API or PayPal
|
165
|
+
def flow_finalize!
|
166
|
+
finalize! unless state == 'complete'
|
167
|
+
update_column :payment_state, 'paid' if payment_state != 'paid'
|
168
|
+
update_column :state, 'complete' if state != 'complete'
|
169
|
+
end
|
170
|
+
|
171
|
+
def flow_payment_method
|
172
|
+
if flow_data['payment_type'] == 'paypal'
|
173
|
+
'paypal'
|
174
|
+
else
|
175
|
+
'cc' # creait card is default
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Spree
|
4
|
+
module PromotionHandler
|
5
|
+
Coupon.class_eval do
|
6
|
+
def apply
|
7
|
+
if order.coupon_code.present?
|
8
|
+
if promotion&.actions.exists?
|
9
|
+
experience_key = order.flow_order&.dig('experience', 'key')
|
10
|
+
forbiden_keys = promotion.flow_data&.dig('filter', 'experience') || []
|
11
|
+
|
12
|
+
if experience_key.present? && !forbiden_keys.include?(experience_key)
|
13
|
+
self.error = 'Promotion is not available in current country'
|
14
|
+
else
|
15
|
+
handle_present_promotion(promotion)
|
16
|
+
end
|
17
|
+
else
|
18
|
+
self.error = if Promotion.with_coupon_code(order.coupon_code)&.expired?
|
19
|
+
Spree.t(:coupon_code_expired)
|
20
|
+
else
|
21
|
+
Spree.t(:coupon_code_not_found)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
self
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# added flow specific methods to Spree.user_class
|
4
|
+
# which is for Spree in same time
|
5
|
+
# - user object (for admins as well)
|
6
|
+
# - customer object
|
7
|
+
|
8
|
+
Spree.user_class.class_eval do
|
9
|
+
def flow_number
|
10
|
+
return unless id
|
11
|
+
|
12
|
+
token = ENV.fetch('ENCRYPTION_KEY')
|
13
|
+
"su-#{Digest::SHA1.hexdigest(format('%d-%s', id, token))}"
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# module Spree
|
4
|
+
# Taxon.class_eval do
|
5
|
+
# def products_by_zone(product_zone)
|
6
|
+
# flow_experience_key = product_zone.flow_data&.[]('key')
|
7
|
+
# sku_regex = product_zone.sku_regex
|
8
|
+
#
|
9
|
+
# if flow_experience_key.present?
|
10
|
+
# products_by_experience(product_zone, sku_regex)
|
11
|
+
# else
|
12
|
+
# products.joins(:master).where('spree_variants.sku ~ ?', sku_regex)
|
13
|
+
# end
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# def products_by_experience(flow_experience_key, sku_regex)
|
17
|
+
# # To make the following query return a distinct array of products, raw SQL had to be used:
|
18
|
+
# # object.products.joins(:variants).where(
|
19
|
+
# # "spree_variants.meta -> 'flow_data' -> 'exp' ->> '#{flow_experience_key}' IS NOT NULL"
|
20
|
+
# # )
|
21
|
+
# query = <<~SQL
|
22
|
+
# SELECT DISTINCT spree_products.* FROM spree_products
|
23
|
+
# INNER JOIN spree_variants ON spree_variants.product_id = spree_products.id AND
|
24
|
+
# spree_variants.is_master = 'f' AND spree_variants.deleted_at IS NULL AND
|
25
|
+
# (spree_variants.sku ~ '#{sku_regex}')
|
26
|
+
# INNER JOIN (
|
27
|
+
# SELECT spree_products_taxons.*, spree_products_taxons.position as position from spree_products_taxons
|
28
|
+
# ORDER BY position ASC
|
29
|
+
# ) I2 ON spree_products.id = I2.product_id
|
30
|
+
# WHERE spree_products.deleted_at IS NULL AND I2.taxon_id = #{id} AND
|
31
|
+
# (spree_variants.meta -> 'flow_data' -> 'exp' ->> '#{flow_experience_key}' IS NOT NULL)
|
32
|
+
# SQL
|
33
|
+
#
|
34
|
+
# Spree::Product.find_by_sql(query)
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
# end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Spree
|
4
|
+
module Zones
|
5
|
+
module FlowIoProductZoneDecorator
|
6
|
+
def self.prepended(base)
|
7
|
+
base.after_update :update_on_flow, if: -> { flow_data&.[]('key').present? }
|
8
|
+
base.before_destroy :remove_on_flow_io, if: -> { flow_data&.[]('key').present? }
|
9
|
+
end
|
10
|
+
|
11
|
+
def available_currencies
|
12
|
+
((currencies || []) + [flow_data&.[]('currency')]).compact.uniq.reject(&:empty?)
|
13
|
+
end
|
14
|
+
|
15
|
+
def flow_io_experience
|
16
|
+
flow_data&.[]('key')
|
17
|
+
end
|
18
|
+
|
19
|
+
def flow_io_experience_currency
|
20
|
+
flow_data&.[]('currency')
|
21
|
+
end
|
22
|
+
|
23
|
+
def flow_io_active_experience?
|
24
|
+
flow_data&.[]('key').present? && flow_data['status'] == 'active'
|
25
|
+
end
|
26
|
+
|
27
|
+
def update_on_flow; end
|
28
|
+
|
29
|
+
def remove_on_flow_io
|
30
|
+
client = FlowcommerceSpree.client
|
31
|
+
client.experiences.delete_by_key(FlowcommerceSpree::ORGANIZATION, flow_data['key'])
|
32
|
+
|
33
|
+
# Flowcommerce `delete_by_key` methods are always returning `nil`, that's why this hack of fetching
|
34
|
+
# @http_handler from client. This handler is a LoggingHttpHandler, which got the http_client attr_reader
|
35
|
+
# implemented specifically for this purpose.
|
36
|
+
false if client.instance_variable_get(:@http_handler).http_client.error
|
37
|
+
end
|
38
|
+
|
39
|
+
def store_flow_io_data(received_experience, logger: FlowcommerceSpree.logger)
|
40
|
+
self.flow_data = received_experience.is_a?(Hash) ? received_experience : received_experience.to_hash
|
41
|
+
self.status = flow_data['status']
|
42
|
+
|
43
|
+
if new_record? && update_attributes(meta: meta, status: status, kind: 'country')
|
44
|
+
logger.info "\nNew flow.io experience imported as product zone: #{name}"
|
45
|
+
elsif update_columns(meta: meta.to_json, status: status, kind: 'country')
|
46
|
+
logger.info "\nProduct zone `#{name}` has been updated from flow.io"
|
47
|
+
end
|
48
|
+
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
Spree::Zones::Product.prepend(self) if Spree::Zones::Product.included_modules.exclude?(self)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FlowcommerceSpree
|
4
|
+
# A service object to import the data for product variants belonging to a flow.io Experience
|
5
|
+
class ImportExperienceItems
|
6
|
+
def self.run(zone, client: FlowcommerceSpree.client, organization: ORGANIZATION)
|
7
|
+
new(zone, client: client, organization: organization).run
|
8
|
+
end
|
9
|
+
|
10
|
+
def run
|
11
|
+
page_size = 100
|
12
|
+
offset = 0
|
13
|
+
items = []
|
14
|
+
total = 0
|
15
|
+
|
16
|
+
while offset == 0 || items.length == 100
|
17
|
+
# show current list size
|
18
|
+
@logger.info "\nGetting items: #{@experience_key.green}, rows #{offset} - #{offset + page_size}"
|
19
|
+
|
20
|
+
begin
|
21
|
+
items = @client.experiences
|
22
|
+
.get_items(@organization, experience: @experience_key, limit: page_size, offset: offset)
|
23
|
+
rescue Io::Flow::V0::HttpClient::PreconditionException => e
|
24
|
+
@logger.info "flow.io API error: #{e.message}"
|
25
|
+
break
|
26
|
+
end
|
27
|
+
|
28
|
+
offset += page_size
|
29
|
+
log_str = +''
|
30
|
+
|
31
|
+
items.each do |item|
|
32
|
+
total += 1
|
33
|
+
item_hash = item.to_hash
|
34
|
+
next unless (variant = Spree::Variant.find_by(sku: item_hash.delete(:number)))
|
35
|
+
|
36
|
+
status_in_experience = item_hash.dig(:local, :status)
|
37
|
+
|
38
|
+
if status_in_experience != 'included'
|
39
|
+
log_str << "[#{status_in_experience.red}]:"
|
40
|
+
else # If at least a variant is included in experience, include the product too
|
41
|
+
adjust_product_zone(variant)
|
42
|
+
end
|
43
|
+
|
44
|
+
variant.flow_import_item(item_hash, experience_key: @experience_key)
|
45
|
+
|
46
|
+
log_str << "#{variant.sku}, "
|
47
|
+
end
|
48
|
+
@logger.info log_str
|
49
|
+
end
|
50
|
+
|
51
|
+
@logger.info "\nData for #{total.to_s.green} products was imported."
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def initialize(zone, client:, organization:)
|
57
|
+
@client = client
|
58
|
+
@experience_key = zone.flow_io_experience
|
59
|
+
@logger = client.instance_variable_get(:@http_handler).logger
|
60
|
+
@organization = organization
|
61
|
+
@zone = zone
|
62
|
+
end
|
63
|
+
|
64
|
+
def adjust_product_zone(variant)
|
65
|
+
return unless (product = variant.product)
|
66
|
+
|
67
|
+
zone_ids = product.zone_ids || []
|
68
|
+
zone_id_string = @zone.id.to_s
|
69
|
+
return if zone_ids.include?(zone_id_string)
|
70
|
+
|
71
|
+
zone_ids << zone_id_string
|
72
|
+
product.zone_ids = zone_ids
|
73
|
+
product.update_columns(meta: product.meta.to_json)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FlowcommerceSpree
|
4
|
+
# A service object to import the data for of flow.io Experience into Spree::Zones::Product
|
5
|
+
class ImportExperiences
|
6
|
+
def self.run(client: FlowcommerceSpree.client, organization: ORGANIZATION, with_items: nil, refresher: nil)
|
7
|
+
new(client: client, organization: organization, with_items: with_items, refresher: refresher).run
|
8
|
+
end
|
9
|
+
|
10
|
+
def run
|
11
|
+
# we have to log start, so that another process does not start while this one is running
|
12
|
+
@refresher.log_refresh!
|
13
|
+
|
14
|
+
@client.experiences.get(@organization, status: 'active').each do |experience|
|
15
|
+
experience_key = experience.key
|
16
|
+
zone = Spree::Zones::Product.find_or_initialize_by(name: experience_key.titleize)
|
17
|
+
zone.store_flow_io_data(experience, logger: @refresher.logger)
|
18
|
+
|
19
|
+
next @refresher.logger.info "Error: storing flow.io experience #{experience_key}" if zone.errors.any?
|
20
|
+
|
21
|
+
ImportExperienceItems.run(zone, client: @client) if @with_items
|
22
|
+
end
|
23
|
+
|
24
|
+
# Log sync end time
|
25
|
+
@refresher.log_refresh!(has_ended: true)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def initialize(client:, organization:, with_items: nil, refresher: Refresher.new)
|
31
|
+
@refresher = refresher
|
32
|
+
@client = client
|
33
|
+
@organization = organization
|
34
|
+
@with_items = with_items
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|