🤝
Masterplan
Data Structure
Data Structure
SKU relation
module Services
module Shopify
class ProductServiceBase < ServiceBase
def find_by(id: nil, handle: nil)
raise 'You can either find a product by id or by handle' if id.nil? && handle.nil?
product_id = id || find_id_for(handle: handle)
ShopifyAPI::Product.find(product_id)
end
def find_id_for(handle:)
response = product_request(handle)
raise "Product request failed with: #{response}" unless response.success?
extract_id_from_product_response(response.parsed_response)
end
def product_request(handle)
query = product_query_for_handle(handle)
@graphql_service.query(query)
end
def product_query_for_handle(handle)
"query {
productByHandle(handle: \"#{handle.downcase}\") {
id
}
}"
end
Price filter
module Mappers
class ErpProductTagMapper
PRICE_THRESHOLDS = %w[
10 25 50 75
100 200 300 400 500 600 700 800 900
1000 1500 2000 2500 3000 3500 4000 4500 5000 7500
10000
].freeze
def self.to_shopify_tags(prices:)
prices.inject([]) do |tags, price|
tags | lower_bound_tags_for_price(price) | upper_bound_tags_for_price(price)
end
end
def self.lower_bound_tags_for_price(price)
lower_bound_tags = price_thresholds.select { |threshold| price.to_f >= threshold.to_f }
lower_bound_tags.map { |tag| "price:gte#{tag}" }
end
Shipping costs
module Mappers
class ErpProductMapper
WEIGHT_MAP = {
'0,0001' => 1,
'1' => 10,
'100' => 10_000,
'1000' => 80_000,
'10000' => 640_000,
'45000' => 5_120_000,
'100000' => 40_960_000
}.freeze
def self.map_weight(erp_data)
WEIGHT_MAP[erp_data['weight']] || raise("unmapped weight #{erp_data['weight']}")
end
def self.map_weight_unit(_erp_data)
'g'
end
Taxes
module Handlers
class ErpTaxCategoryHandler < Handlers::HandlerBase
def handle(product_data:)
product_handle = @akeneo_service.extract_product_handle(product_data)
return if product_handle.nil?
tax_collection_handle = extract_tax_collection_handle(product_data)
response = @shopify_service.add_product_to_collection(tax_collection_handle, product_handle)
return unless response.nil?
return if @shopify_service.collection_exists?(tax_collection_handle)
raise "missing collection '#{tax_collection_handle}', please create it manually"
end
private
def extract_tax_collection_handle(erp_data)
"tax-#{erp_data['tax'].tr(',', '-')}"
end
end
end
Logging
require 'semantic_logger'
SemanticLogger.add_appender(io: STDOUT, formatter: :json)
class ServiceBase
include SemanticLogger::Loggable
end
logger.info(handle: product_handle)
Metafields
- Files
- Associations
- Basic price
- Store availability
- Hreflang
Akeneo gem
github.com/awniemeyer/akeneo
module Akeneo
class ProductService < ServiceBase
def brothers_and_sisters(id)
akeneo_product = find(id)
akeneo_parent = load_akeneo_parent(akeneo_product['parent'])
akeneo_grand_parent = load_akeneo_parent(akeneo_parent['parent']) unless akeneo_parent.nil?
parents = load_parents(akeneo_product['family'], akeneo_parent, akeneo_grand_parent)
load_products(akeneo_product, akeneo_product['family'], parents)
end
Shopify theme development
Cool Features
- Seperate product and stock sync
- I18n
- Broke tight coupling
Thanks for listening!
Questions?
Stay healthy
😷