module Seshbot::Packing

Constants

VERSION

Public Class Methods

_bundle_items_legacy(recipes, items) click to toggle source
# File lib/seshbot/packing/package.rb, line 122
def _bundle_items_legacy(recipes, items)
  # get the cans first, and separate them out
  cans = _filter_by_sku_fragment_prefix(items, 'C')
  remaining_cans = []
  unless cans.empty?
    bundle_items_by_type = LineItem.merge_line_items(cans)
    bundle_items_by_type = unpack(recipes, bundle_items_by_type)
    new_can_items = pack(recipes, bundle_items_by_type)
    # separate out all the C324s
    separated_c324s = _filter_by_sku_fragment_prefix(new_can_items, 'C324')
    logger.debug "bundling - Cans: #{cans.map(&:to_s)}"
    logger.debug " - Removed C324s: #{separated_c324s.map(&:to_s)}"

    remaining_cans = _filter_by_sku_fragment_prefix(new_can_items, 'C324', inverse: true)
    logger.debug " - Remaining skus: #{remaining_cans.map(&:to_s)}"
  end

  # merge the remaining with the original leftover
  non_cans = _filter_by_sku_fragment_prefix(items, 'C', inverse: true)
  remaining_items = non_cans + remaining_cans
  logger.debug "bundling - Substituting:"
  logger.debug " - remaining items: #{remaining_items.map(&:to_s)}"

  # substitute C's for B's
  remaining_items = _substitute_sku(remaining_items, /^C/, 'B')
  logger.debug " - skus updated to: #{remaining_items.map(&:to_s)}"

  # get a hash of {pack_6: 5, pack_12: 1}
  bundle_items_by_type = LineItem.merge_line_items(remaining_items)
  # dismantle packages into individual units (e.g., {pack_6: 7})
  bundle_items_by_type = unpack(recipes, bundle_items_by_type)
  # repackage into the 'best' packaging we can figure out (e.g., {pack_12: 2})
  new_remaining_items = pack(recipes, bundle_items_by_type)

  separated_c324s ||= []
  results = new_remaining_items + separated_c324s

  results
end
_bundle_items_single_phase(recipes, items) click to toggle source
# File lib/seshbot/packing/package.rb, line 162
def _bundle_items_single_phase(recipes, items)
  # get a list of items merged together by variant type (e.g., 10xBIGI-C301 + 5xBISS-C301 => 15xBUND-C301))
  merged_items = LineItem.merge_line_items(items)
  # dismantle packages into individual units
  unpacked_items = unpack(recipes, merged_items)
  # repackage into the 'best' packaging we can figure out
  pack(recipes, unpacked_items)
end
_filter_by_sku_fragment_prefix(items, sku_fragment, inverse: false) click to toggle source
# File lib/seshbot/packing/package.rb, line 171
def _filter_by_sku_fragment_prefix(items, sku_fragment, inverse: false)
  items.select do |i|
    matches = i.sku_fragment.start_with? sku_fragment
    inverse ? !matches : matches
  end
end
_is_legacy(fulfilled_at) click to toggle source

private

# File lib/seshbot/packing/package.rb, line 116
def _is_legacy(fulfilled_at)
  effective_fulfilled_at = fulfilled_at.nil? ? DateTime.now : fulfilled_at
  is_legacy = effective_fulfilled_at < DateTime.parse('2021-04-23T08:00:00+09:00')
  is_legacy
end
_pack_single_step(recipes, items, unpacking:) click to toggle source
# File lib/seshbot/packing/package.rb, line 182
def _pack_single_step(recipes, items, unpacking:)
  recipe = Recipe::find_best_recipe(recipes, items, unpacking: unpacking)
  if recipe.nil?
    # its as packed/unpacked as it can get
    return items
  end

  before = LineItem::summarise(items)
  items = Recipe::apply_recipe(recipe, items)
  after = LineItem::summarise(items)
  logger.debug "bundling - applying recipe (#{unpacking ? 'unpacking' : 'packing'}) #{Recipe::summarise(recipe)}: #{before} -> #{after}"
  items
end
_substitute_sku(items, re, sub) click to toggle source
# File lib/seshbot/packing/package.rb, line 178
def _substitute_sku(items, re, sub)
  items.map { |i| LineItem.new(i.sku_fragment.gsub(re, sub), i.quantity) }
end
bundle_items(recipes, items, fulfilled_at: nil) click to toggle source
# File lib/seshbot/packing/package.rb, line 21
def bundle_items(recipes, items, fulfilled_at: nil)
  if !fulfilled_at.nil? && fulfilled_at < DateTime.parse('2020-01-14T08:00:00+09:00')
    logger.debug "bundling - Not Bundling (pre-2020 order) =="
    return items
  end
  is_legacy = _is_legacy(fulfilled_at)
  is_legacy_str = is_legacy ? " (LEGACY)" : ""

  logger.debug "bundling - Bundling Items: #{items.map(&:to_s)}. Fulfilled at: #{fulfilled_at || '(no fulfillment date)'}#{is_legacy_str}"

  results = []

  if is_legacy
    results = _bundle_items_legacy(recipes, items)
  else
    phases = recipes.values.map { |r| r['phase'] || 0 }.map(&:to_i).uniq.sort

    results = items
    phases.each do |phase|
      phase_recipes = recipes.select { |name, recipe| (recipe['phase'] || 0) == phase }.to_h

      logger.debug "bundling - PHASE #{phase}"

      results = _bundle_items_single_phase(phase_recipes, results)
    end
  end

  logger.debug "bundling - Bundled result:"
  logger.debug " - IN: #{items.map(&:to_s)}"
  logger.debug " - OUT: #{results.map(&:to_s)}"

  results
end
logger() click to toggle source
# File lib/seshbot/packing/package.rb, line 9
def logger
  if @@logger.nil?
    if defined?(Rails)
      @@logger = Rails.logger
    else
      @@logger = Logger.new(STDERR)
      @@logger.level = 1
    end
  end
  @@logger
end
pack(recipes, items) click to toggle source
# File lib/seshbot/packing/package.rb, line 95
def pack(recipes, items)
  prev_result = items

  # keep trying to 'pack' until it stabilises (e.g., 6x6pack => 1x24pack+2x6pack => 1x24pack+1x12pack)
  (1..1000).each do |i|
    new_result = _pack_single_step(recipes, prev_result, unpacking: false)
    # no changes, break out
    is_unchanged = new_result.map { |li| [li.sku_fragment, li.quantity] }.sort == prev_result.map { |li| [li.sku_fragment, li.quantity] }.sort
    return new_result if is_unchanged

    prev_result = new_result
  end
  error_message = "bundling - could not pack - infinite loop? (latest: #{prev_result.map(&:to_s)})"
  logger.error error_message
  raise error_message
end
unbundle_items(recipes, items, fulfilled_at: nil) click to toggle source
# File lib/seshbot/packing/package.rb, line 55
def unbundle_items(recipes, items, fulfilled_at: nil)
  logger.debug "bundling - Unbundling Items: #{items.map(&:to_s)}"

  results = []

  phases = recipes.values.map { |r| r['phase'] || 0 }.map(&:to_i).uniq.sort.reverse

  results = items
  phases.each do |phase|
    phase_recipes = recipes.select { |name, recipe| (recipe['phase'] || 0) == phase }.to_h

    logger.debug "bundling - PHASE #{phase}"

    # get a list of items merged together by variant type (e.g., 10xBIGI-C301 + 5xBISS-C301 => 15xBUND-C301))
    merged_items = LineItem.merge_line_items(results)
    results = unpack(recipes, merged_items)
  end

  logger.debug "bundling - Unbundle result:"
  logger.debug " - IN: #{items.map(&:to_s)}"
  logger.debug " - OUT: #{results.map(&:to_s)}"

  results
end
unpack(recipes, items) click to toggle source
# File lib/seshbot/packing/package.rb, line 80
def unpack(recipes, items)
  prev_result = items

  (1..1000).each do |i|
    new_result = _pack_single_step(recipes, prev_result, unpacking: true)
    is_unchanged = new_result.map { |li| [li.sku_fragment, li.quantity] }.sort == prev_result.map { |li| [li.sku_fragment, li.quantity] }.sort
    return new_result if is_unchanged

    prev_result = new_result
  end
  error_message = "bundling - could not unpack - infinite loop? (latest: #{prev_result.map(&:to_s)})"
  logger.error error_message
  raise error_message
end