order_optimizer 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d3dec3fd3cab38f6a56c1fa13e3ee398e4b196c1c992cdf6f4c7e87c2c19e2f1
4
- data.tar.gz: 9cd52e4ae25d980de2b8b4ba1988bbe42dac0dd686d0d1c7f23584c7889cb3e8
3
+ metadata.gz: c7436b34e3961c7cb835d33eb4212ce823fc050d362af7b3297eb80a2a5dda77
4
+ data.tar.gz: 366e5d4ba6d0fb9772a982ec0aeccf6aba82cc3e8bb79237954afdfebc42a5da
5
5
  SHA512:
6
- metadata.gz: 958f1f99429e00465cdc9a562dff1717da0b145827430704766756626444559e25f1f0b615c7f19cb274bc1783e33fb2eb3ca34b8e4d114e08af5f01f75c05f2
7
- data.tar.gz: 0cfb7dc00f8eacff31bff96cfedee003427e9e9e4677f8a382f7aa28e0148de5ca902ca576697aeba54c3b28a19a3302f451fb0944dc96464908742a9328287f
6
+ metadata.gz: cf15bc0ee13c1f1c244b9d5a54b7271f1f6fee150305411bab9f18353d80f701e148c86eeda665ab555222f4a0cd10f5a8370d18910f190dc886080fc0883e03
7
+ data.tar.gz: 0aa94e6ce45593754e611376e9a29389caa3ae1d167f3406d7ef232a0906918802dc6515bff0f63cc3d8830d5d863c1ed316f6197457719c2aa974afc7a3051b
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # OrderOptimizer Gem Changelog
2
2
 
3
+ ## 0.2.1 (2019-12-18)
4
+
5
+ - Improve result for mixed catalogs with discounts and multiple pack sizes
6
+
3
7
  ## 0.2.0 (2019-12-16)
4
8
 
5
9
  - Add support for discounts when ordering a minimum quantity
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- order_optimizer (0.1.0)
4
+ order_optimizer (0.2.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -1,8 +1,8 @@
1
1
  require 'bigdecimal'
2
2
 
3
- require "order_optimizer/catalog"
4
- require "order_optimizer/order"
5
- require "order_optimizer/version"
3
+ require 'order_optimizer/catalog'
4
+ require 'order_optimizer/order'
5
+ require 'order_optimizer/version'
6
6
 
7
7
  class OrderOptimizer
8
8
  def initialize(skus)
@@ -10,39 +10,47 @@ class OrderOptimizer
10
10
  end
11
11
 
12
12
  def cheapest_order(required_qty:)
13
- orders =
14
- possible_orders(skus: @catalog.skus_without_min_quantities, required_qty: required_qty) +
15
- possible_orders(skus: @catalog.skus_with_min_quantities, required_qty: required_qty) +
16
- possible_orders(skus: @catalog.skus, required_qty: required_qty)
17
-
18
- orders.min_by(&:total) || OrderOptimizer::Order.new
13
+ possible_orders(skus: @catalog.skus, required_qty: required_qty)
14
+ .min_by(&:total) || OrderOptimizer::Order.new(required_qty: required_qty)
19
15
  end
20
16
 
21
17
  private
22
18
 
23
19
  def possible_orders(required_qty:, skus:)
24
- [].tap do |orders|
25
- order = OrderOptimizer::Order.new
20
+ return [] if required_qty < 1 || skus.empty?
21
+
22
+ orders = []
26
23
 
27
- while required_qty.positive? && (sku = skus.shift)
28
- count, remainder = (required_qty - order.quantity).divmod(sku.quantity)
29
- count, remainder = adjustment_for_skus_with_min_quantity(sku, count, remainder)
24
+ skus.each do |sku|
25
+ orders.reject(&:complete?).each do |order|
26
+ count, remainder = count_and_remainder_for_sku(order.missing_qty, sku)
30
27
 
31
- order.add(sku, count: count) unless count.zero?
28
+ orders << order.dup.add(sku, count: count) unless count.zero?
29
+ orders << order.dup.add(sku, count: count + 1) if remainder
30
+ end
32
31
 
33
- orders << (remainder.positive? ? order.dup.add(sku) : order.dup)
32
+ count, remainder = count_and_remainder_for_sku(required_qty, sku)
34
33
 
35
- order = OrderOptimizer::Order.new if sku.min_quantity
34
+ unless count.zero?
35
+ orders << OrderOptimizer::Order.new(required_qty: required_qty).add(sku, count: count)
36
+ end
37
+ if remainder
38
+ orders << OrderOptimizer::Order.new(required_qty: required_qty).add(sku, count: count + 1)
36
39
  end
37
40
  end
38
- end
39
41
 
40
- def adjustment_for_skus_with_min_quantity(sku, count, remainder)
41
- units = count * sku.quantity
42
+ orders.select(&:complete?)
43
+ end
42
44
 
43
- return count, remainder if sku.min_quantity.nil? || units >= sku.min_quantity
45
+ def count_and_remainder_for_sku(quantity, sku)
46
+ count, remainder = quantity.divmod(sku.quantity)
44
47
 
45
- delta = ((sku.min_quantity - units).to_f / sku.quantity).ceil
46
- [count + delta, remainder - (delta * sku.quantity)]
48
+ if sku.min_quantity && count * sku.quantity < sku.min_quantity
49
+ new_count = (sku.min_quantity.to_f / sku.quantity).ceil
50
+ remainder -= (new_count - count) * sku.quantity
51
+ [new_count, [remainder, 0].max]
52
+ else
53
+ [count, remainder]
54
+ end
47
55
  end
48
56
  end
@@ -9,13 +9,5 @@ class OrderOptimizer
9
9
  def skus
10
10
  @skus.dup
11
11
  end
12
-
13
- def skus_without_min_quantities
14
- @skus.reject(&:min_quantity)
15
- end
16
-
17
- def skus_with_min_quantities
18
- @skus.select(&:min_quantity)
19
- end
20
12
  end
21
13
  end
@@ -2,17 +2,26 @@ class OrderOptimizer
2
2
  class Order
3
3
  attr_reader :quantity, :total, :skus
4
4
 
5
- def initialize
6
- @quantity = 0
7
- @total = 0
8
- @skus = {}
5
+ def initialize(required_qty:)
6
+ @quantity = 0
7
+ @required_qty = required_qty
8
+ @total = 0
9
+ @skus = {}
9
10
  end
10
11
 
11
12
  def add(sku, count: 1)
12
13
  @quantity += count * sku.quantity
13
14
  @total += count * sku.price_per_sku
14
- @skus = skus.merge(sku.id => count) { |identifier, current, plus| current + plus }
15
+ @skus = skus.merge(sku.id => count) { |_identifier, current, plus| current + plus }
15
16
  self
16
17
  end
18
+
19
+ def missing_qty
20
+ [@required_qty - quantity, 0].max
21
+ end
22
+
23
+ def complete?
24
+ missing_qty.zero?
25
+ end
17
26
  end
18
27
  end
@@ -2,24 +2,20 @@ class OrderOptimizer
2
2
  class Sku
3
3
  attr_reader :id, :quantity, :price_per_unit, :price_per_sku, :min_quantity
4
4
 
5
- def initialize(id, quantity:, price_per_unit:, min_quantity: nil)
5
+ def initialize(id, quantity:, price_per_sku: nil, price_per_unit: nil, min_quantity: nil)
6
6
  @id = id
7
7
  @quantity = quantity
8
- @price_per_unit = BigDecimal(price_per_unit, 2)
9
- @price_per_sku = quantity * price_per_unit
10
8
  @min_quantity = min_quantity
11
- end
12
-
13
- private
14
9
 
15
- def set_min_quantity(min_qty)
16
- return unless min_qty
10
+ @price_per_unit = BigDecimal(price_per_unit, 2) if price_per_unit
11
+ @price_per_sku = BigDecimal(price_per_sku, 2) if price_per_sku
17
12
 
18
- if min_quantity.modulo(quantity).zero?
19
- @min_quantity = min_qty
20
- else
21
- raise ':min_quantity must be a multiple of :quantity'
13
+ unless price_per_unit || price_per_sku
14
+ raise ':price_per_sku or :price_per_unit must be set'
22
15
  end
16
+
17
+ @price_per_unit ||= price_per_sku.to_f / quantity
18
+ @price_per_sku ||= quantity * price_per_unit
23
19
  end
24
20
  end
25
21
  end
@@ -1,3 +1,3 @@
1
1
  class OrderOptimizer
2
- VERSION = "0.2.0"
2
+ VERSION = "0.2.1"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: order_optimizer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - crispymtn
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2019-12-16 00:00:00.000000000 Z
12
+ date: 2019-12-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler