Module: Spree::FlowIoVariantDecorator

Defined in:
app/models/spree/flow_io_variant_decorator.rb

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.prepended(base) ⇒ Object



8
9
10
11
12
13
14
15
# File 'app/models/spree/flow_io_variant_decorator.rb', line 8

def self.prepended(base)
  base.serialize :meta, ActiveRecord::Coders::JSON.new(symbolize_keys: true)

  base.store_accessor :meta, :flow_data

  # after every save we sync product we generate sh1 checksums to update only when change happend
  base.after_save :sync_product_to_flow
end

Instance Method Details

#add_flow_io_experience_data(exp, value) ⇒ Object

Raises:

  • (ArgumentError)


21
22
23
24
25
26
27
# File 'app/models/spree/flow_io_variant_decorator.rb', line 21

def add_flow_io_experience_data(exp, value)
  raise ArgumentError, 'Value should be a hash' unless value.is_a?(Hash)

  self.flow_data = flow_data || {}
  self.flow_data['exp'] ||= {} # rubocop:disable Style/RedundantSelf
  self.flow_data['exp'][exp] = value # rubocop:disable Style/RedundantSelf
end

#all_prices_in_zone(product_zone) ⇒ Object



128
129
130
131
132
133
134
135
136
137
# File 'app/models/spree/flow_io_variant_decorator.rb', line 128

def all_prices_in_zone(product_zone)
  all_prices = prices.map { |price| { currency: price.currency, amount: (price.amount&.round || 0).to_s } }

  flow_experience_key = product_zone&.flow_data&.[]('key')
  return all_prices if flow_experience_key.blank?

  flow_price = flow_local_price(flow_experience_key)
  all_prices << { currency: flow_price.currency, amount: (flow_price.amount&.round || 0).to_s }
  all_prices
end

#common_attrs(taxon) ⇒ Object



180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'app/models/spree/flow_io_variant_decorator.rb', line 180

def common_attrs(taxon)
  {
    weight: weight.to_s,
    height: height.to_s,
    width: width.to_s,
    depth: depth.to_s,
    is_master: is_master ? 'true' : 'false',
    product_id: product_id.to_s,
    tax_category: product.tax_category_id.to_s,
    product_description: product.description,
    product_shipping_category: product.shipping_category_id ? shipping_category.name : nil,
    product_meta_title: taxon&.meta_title.to_s,
    product_meta_description: taxon&.meta_description.to_s,
    product_meta_keywords: taxon&.meta_keywords.to_s,
    product_slug: product.slug
  }.select { |_k, v| v.present? }
end

#experiencesObject



17
18
19
# File 'app/models/spree/flow_io_variant_decorator.rb', line 17

def experiences
  flow_data&.[]('exp')
end

#flow_import_item(item_hash, experience_key: nil) ⇒ Object

gets flow catalog item, and imports it called from flow:sync_localized_items rake task



200
201
202
203
204
205
206
207
208
209
210
211
# File 'app/models/spree/flow_io_variant_decorator.rb', line 200

def flow_import_item(item_hash, experience_key: nil)
  # If experience not specified, get it from the local hash of imported variant
  experience_key ||= item_hash.dig(:local, :experience, :key)
  current_experience_meta = item_hash.delete(:local)

  # Do not repeatedly store Experience data - this is stored in Spree::Zones::Product
  current_experience_meta.delete(:experience)
  add_flow_io_experience_data(experience_key, current_experience_meta)
  self.flow_data.merge!(item_hash)

  update_column(:meta, meta.to_json)
end

#flow_local_price(flow_exp) ⇒ Object

returns price bound to local experience



114
115
116
117
118
119
# File 'app/models/spree/flow_io_variant_decorator.rb', line 114

def flow_local_price(flow_exp)
  price_object = flow_prices(flow_exp)&.first
  amount = price_object&.[](:amount) || price
  currency = price_object&.[](:currency) || cost_currency
  Spree::Price.new(variant_id: id, currency: currency, amount: amount)
end

#flow_prices(flow_exp) ⇒ Object



109
110
111
# File 'app/models/spree/flow_io_variant_decorator.rb', line 109

def flow_prices(flow_exp)
  flow_data&.dig(:exp, flow_exp, :prices) || []
end

#price_in_zone(currency, product_zone) ⇒ Object



121
122
123
124
125
126
# File 'app/models/spree/flow_io_variant_decorator.rb', line 121

def price_in_zone(currency, product_zone)
  flow_experience_key = product_zone&.flow_data&.[]('key')
  return flow_local_price(flow_experience_key) if flow_experience_key.present?

  price_in(currency)
end

#remove_experience_from_product(exp_key, product) ⇒ Object



41
42
43
44
45
46
47
48
49
50
# File 'app/models/spree/flow_io_variant_decorator.rb', line 41

def remove_experience_from_product(exp_key, product)
  return unless (zone = Spree::Zones::Product.find_by(name: exp_key.titleize))

  zone_ids = product.zone_ids || []
  zone_id_string = zone.id.to_s
  return unless zone_ids.include?(zone_id_string)

  product.zone_ids = zone_ids - [zone_id_string]
  product.update_columns(meta: product.meta.to_json)
end

#sync_flow_info?Boolean

Returns:

  • (Boolean)


52
53
54
55
56
57
58
# File 'app/models/spree/flow_io_variant_decorator.rb', line 52

def sync_flow_info?
  if FlowcommerceSpree::API_KEY.blank? || FlowcommerceSpree::API_KEY == 'test_key'
    return { error: 'Api Keys not configured' }
  end
  return { error: 'Price is 0' } if price == 0
  return { error: 'Country of Origin is empty.' } unless country_of_origin
end

#sync_product_to_flowObject

upload product variant to Flow's Product Catalog



61
62
63
64
65
66
# File 'app/models/spree/flow_io_variant_decorator.rb', line 61

def sync_product_to_flow
  error = sync_flow_info?
  return error if error.present?

  update_flow_data
end

#to_flowcommerce_item(additional_attrs) ⇒ Object

creates object for flow api



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
172
173
174
175
176
177
178
# File 'app/models/spree/flow_io_variant_decorator.rb', line 140

def to_flowcommerce_item(additional_attrs)
  # add product categories
  categories = []
  taxon = product.taxons.first
  current_taxon = taxon
  while current_taxon
    categories.unshift current_taxon.name
    current_taxon = current_taxon.parent
  end

  images = if (image = product.images.first || product.variant_images.first)
             asset_host_scheme = ENV.fetch('ASSET_HOST_PROTOCOL', 'https')
             asset_host = ENV.fetch('ASSET_HOST', 'staging.mejuri.com')
             large_image_uri = URI(image.attachment(:large))
             product_image_uri = URI(image.attachment.url(:product))
             large_image_uri.scheme ||= asset_host_scheme
             product_image_uri.scheme ||= asset_host_scheme
             large_image_uri.host ||= asset_host
             product_image_uri.host ||= asset_host

             [{ url: large_image_uri.to_s, tags: ['checkout'] },
              { url: product_image_uri.to_s, tags: ['thumbnail'] }]
           else
             []
           end

  Io::Flow::V0::Models::ItemForm.new(
    number: sku,
    locale: 'en_US',
    language: 'en',
    name: product.name,
    description: product.description,
    currency: cost_currency,
    price: price.to_f,
    images: images,
    categories: categories,
    attributes: common_attrs(taxon).merge!(additional_attrs)
  )
end

#truncate_flow_dataObject

clears flow_data from the records



30
31
32
33
34
35
36
37
38
39
# File 'app/models/spree/flow_io_variant_decorator.rb', line 30

def truncate_flow_data
  flow_data&.[]('exp')&.keys&.each do |exp_key|
    break unless (product = self.product)

    remove_experience_from_product(exp_key, product)
  end

  meta.delete(:flow_data)
  update_column(:meta, meta.to_json)
end

#update_flow_dataObject



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
# File 'app/models/spree/flow_io_variant_decorator.rb', line 68

def update_flow_data
  additional_attrs = {}
  attr_name = nil
  export_required = false
  FlowcommerceSpree::Config.additional_attributes[self.class.name.tableize.tr('/', '_').to_sym]&.each do |attr_item|
    attr_name = attr_item[0]
    # Flow.io could require a different attribute name, as in case of Fulfil's :customs_description - it has the
    # export_name `:materials` for flow.io. That's why 1st we're checking if an export_name is defined for the
    # attribute.
    attr_flowcommerce_name = attr_item[1][:export_name] || attr_name
    export_required = attr_item[1][:export] == :required
    attr_value = __send__(attr_name)
    break if export_required && attr_value.blank?

    additional_attrs[attr_flowcommerce_name] = attr_value if attr_value
  end

  if export_required && additional_attrs[attr_value].blank?
    return { error: "Variant with sku = #{sku} has no #{attr_name}" }
  end

  flow_item     = to_flowcommerce_item(additional_attrs)
  flow_item_sh1 = Digest::SHA1.hexdigest(flow_item.to_json)

  # skip if sync not needed
  return { error: 'Synchronization not needed' } if flow_data&.[](:last_sync_sh1) == flow_item_sh1

  response = FlowcommerceSpree.client.items.put_by_number(FlowcommerceSpree::ORGANIZATION, sku, flow_item)
  self.flow_data ||= {}
  self.flow_data[:last_sync_sh1] = flow_item_sh1

  # after successful put, write cache
  update_column(:meta, meta.to_json)

  FlowcommerceSpree::ImportItemWorker.perform_async(sku)

  response
rescue Net::OpenTimeout => e
  { error: e.message }
end