seshbot-packing 0.8.5 → 0.8.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/lib/seshbot/packing.rb +1 -0
- data/lib/seshbot/packing/line_item.rb +19 -0
- data/lib/seshbot/packing/package.rb +35 -33
- data/lib/seshbot/packing/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c1693c794a4be401fe157bd5a6385e1375e37c738e8919a168cc881ec262f9ec
|
4
|
+
data.tar.gz: 44c25e149bc8dba14cc2cc49f309b5e3ba5901bf4a636f89787ec5e946e28502
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 59f4cb5bad8c401ef6dde61443937c3bbce68f37f659aef074175a067fd6ea91b346531bc1c809d7b2788a30969eb14c92d8f716fc382d56ac26560cec6c93fd
|
7
|
+
data.tar.gz: c9536fe10e717e4841e8521f5986e467cc0e2b029c3a7512cee255f61941f0d0a5041c6ff0a9a047c218691d9b47ef69b14da6df7227f304cc4eb015f0ba1afa
|
data/Gemfile.lock
CHANGED
data/lib/seshbot/packing.rb
CHANGED
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Seshbot
|
4
|
+
module Packing
|
5
|
+
# :nodocs:
|
6
|
+
class LineItem
|
7
|
+
attr_accessor :sku, :quantity, :price
|
8
|
+
|
9
|
+
def initialize(sku, quantity, price=0)
|
10
|
+
raise 'SKU must be a string' if sku.class != String
|
11
|
+
raise 'Quantity must be an integer' if quantity.class != Integer
|
12
|
+
|
13
|
+
@sku = sku
|
14
|
+
@quantity = quantity
|
15
|
+
@price = price
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -5,32 +5,34 @@ module Seshbot
|
|
5
5
|
module Packing
|
6
6
|
class Error < StandardError; end
|
7
7
|
class << self
|
8
|
+
@@logger = nil
|
8
9
|
def logger
|
9
|
-
logger
|
10
|
-
|
11
|
-
|
10
|
+
if @@logger.nil?
|
11
|
+
@@logger = Logger.new(STDERR)
|
12
|
+
@@logger.level = 1
|
13
|
+
end
|
14
|
+
@@logger
|
12
15
|
end
|
13
16
|
|
14
17
|
def filter_by_sku(items, sku_fragment, inverse: false)
|
15
18
|
items.select do |i|
|
16
|
-
matches = i
|
19
|
+
matches = i.sku.include?(sku_fragment)
|
17
20
|
inverse ? !matches : matches
|
18
21
|
end
|
19
22
|
end
|
20
23
|
|
21
24
|
def substitute_sku(items, from, to)
|
22
|
-
items.
|
23
|
-
items
|
25
|
+
items.map { |i| Seshbot::Packing::LineItem.new(i.sku.gsub(from, to), i.quantity) }
|
24
26
|
end
|
25
27
|
|
26
28
|
def count_product_types(items)
|
27
29
|
result = Hash.new(0)
|
28
30
|
# create a hash were the keys are sku_fragment, and the values are the summed quantities
|
29
31
|
items.each do |item|
|
30
|
-
size = packaged_size(item
|
31
|
-
result[size] += item
|
32
|
+
size = packaged_size(item.sku)
|
33
|
+
result[size] += item.quantity
|
32
34
|
end
|
33
|
-
result
|
35
|
+
result.map { |k,v| Seshbot::Packing::LineItem.new("BUND-#{k}", v) }
|
34
36
|
end
|
35
37
|
|
36
38
|
def packaged_size(sku)
|
@@ -58,16 +60,15 @@ module Seshbot
|
|
58
60
|
logger.debug "***** BUNDLING"
|
59
61
|
logger.debug "Bundling Items: #{items}. Fulfilled at: #{fulfilled_at || '(no fulfillment date)'}"
|
60
62
|
if !fulfilled_at.nil? && fulfilled_at < DateTime.new(2020, 1, 14, 8, 0, 0, Time.new.zone)
|
61
|
-
return items
|
63
|
+
return items
|
62
64
|
end
|
63
65
|
|
64
66
|
# get the cans first, and separate them out
|
65
67
|
cans = filter_by_sku(items, '-C')
|
66
68
|
unless cans.empty?
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
new_can_items = product_type_counts.map { |k, v| { 'sku' => "BUND-#{k}", 'quantity' => v, 'price' => 0 } }
|
69
|
+
bundle_items_by_type = count_product_types(cans)
|
70
|
+
bundle_items_by_type = unpack(recipes, bundle_items_by_type)
|
71
|
+
new_can_items = pack(recipes, bundle_items_by_type)
|
71
72
|
# separate out all the C324s
|
72
73
|
separated_c324s = filter_by_sku(new_can_items, '-C324')
|
73
74
|
logger.debug "Cans: #{cans}"
|
@@ -85,16 +86,15 @@ module Seshbot
|
|
85
86
|
logger.debug " - remaining items: #{remaining_items}"
|
86
87
|
|
87
88
|
# substitute C's for B's
|
88
|
-
substitute_sku(remaining_items, '-C', '-B')
|
89
|
+
remaining_items = substitute_sku(remaining_items, '-C', '-B')
|
89
90
|
logger.debug " - skus updated to: #{remaining_items}"
|
90
91
|
|
91
92
|
# get a hash of {pack_6: 5, pack_12: 1}
|
92
|
-
|
93
|
+
bundle_items_by_type = count_product_types(remaining_items)
|
93
94
|
# dismantle packages into individual units (e.g., {pack_6: 7})
|
94
|
-
|
95
|
+
bundle_items_by_type = unpack(recipes, bundle_items_by_type)
|
95
96
|
# repackage into the 'best' packaging we can figure out (e.g., {pack_12: 2})
|
96
|
-
|
97
|
-
new_remaining_items = product_type_counts.map { |k,v| { "sku" => "BUND-#{k}", "quantity" => v, "price" => 0} }
|
97
|
+
new_remaining_items = pack(recipes, bundle_items_by_type)
|
98
98
|
|
99
99
|
separated_c324s ||= []
|
100
100
|
merged_items = new_remaining_items + separated_c324s
|
@@ -104,45 +104,47 @@ module Seshbot
|
|
104
104
|
merged_items
|
105
105
|
end
|
106
106
|
|
107
|
+
def unpack(recipes, items)
|
107
108
|
|
108
|
-
|
109
|
-
final_result = product_type_counts
|
109
|
+
final_result = items
|
110
110
|
|
111
|
-
|
111
|
+
loop do
|
112
112
|
new_result = pack_single_step(recipes, final_result, unpacking: true)
|
113
|
-
break if new_result == final_result
|
113
|
+
break if new_result.map { |li| [li.sku, li.quantity] }.sort == final_result.map { |li| [li.sku, li.quantity] }.sort
|
114
114
|
|
115
115
|
final_result = new_result
|
116
116
|
end
|
117
117
|
final_result
|
118
118
|
end
|
119
119
|
|
120
|
-
def pack_single_step(recipes,
|
120
|
+
def pack_single_step(recipes, items, unpacking:)
|
121
121
|
result = Hash.new(0)
|
122
|
-
|
123
|
-
|
122
|
+
items.each do |item|
|
123
|
+
sku_size = packaged_size(item.sku)
|
124
|
+
recipe = Recipe::find_best_recipe(recipes, sku_size, item.quantity, unpacking: unpacking)
|
124
125
|
if recipe.nil?
|
125
|
-
result[
|
126
|
+
result[sku_size] += item.quantity
|
126
127
|
next
|
127
128
|
end
|
128
129
|
|
129
|
-
recipe_quantities = Recipe::apply_recipe(recipe,
|
130
|
+
recipe_quantities = Recipe::apply_recipe(recipe, item.quantity)
|
130
131
|
result[recipe["input_fragment"]] += recipe_quantities[:input_quantity]
|
131
132
|
result[recipe["output_fragment"]] += recipe_quantities[:output_quantity]
|
132
133
|
end
|
133
134
|
result.delete_if { |k,v| v == 0}
|
134
|
-
result
|
135
|
+
result.map { |k,v| Seshbot::Packing::LineItem.new("BUND-#{k}", v) }
|
135
136
|
end
|
136
137
|
|
137
138
|
|
138
|
-
def pack(recipes,
|
139
|
-
final_result =
|
139
|
+
def pack(recipes, items)
|
140
|
+
final_result = items
|
140
141
|
|
141
142
|
# keep trying to 'pack' until it stabilises (e.g., 6x6pack => 1x24pack+2x6pack => 1x24pack+1x12pack)
|
142
|
-
|
143
|
+
loop do
|
143
144
|
new_result = pack_single_step(recipes, final_result, unpacking: false)
|
144
145
|
# no changes, break out
|
145
|
-
break if new_result == final_result
|
146
|
+
break if new_result.map { |li| [li.sku, li.quantity] }.sort == final_result.map { |li| [li.sku, li.quantity] }.sort
|
147
|
+
|
146
148
|
final_result = new_result
|
147
149
|
end
|
148
150
|
final_result
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: seshbot-packing
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.8.
|
4
|
+
version: 0.8.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shaun
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-03-
|
11
|
+
date: 2021-03-31 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description:
|
14
14
|
email:
|
@@ -28,6 +28,7 @@ files:
|
|
28
28
|
- bin/console
|
29
29
|
- bin/setup
|
30
30
|
- lib/seshbot/packing.rb
|
31
|
+
- lib/seshbot/packing/line_item.rb
|
31
32
|
- lib/seshbot/packing/package.rb
|
32
33
|
- lib/seshbot/packing/recipe.rb
|
33
34
|
- lib/seshbot/packing/version.rb
|