Ecommerce
✨🛍✨
Renewal

🤝

Legacy Landscape



Masterplan

Architecture Plan

Data Structures

Data Structure

Akeneo data structure

Data Structure

Shopify data structure

SKU relation

Relation of SKU between products
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

😷