order_optimizer 0.1.0 → 0.2.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f0845a565bc344866e5e3d4725d28b890c9f3e4d57de36ee2789ee331f000168
4
- data.tar.gz: b825661a3746a8b7bf574c3938efb2b412ac81163223a4836be013dda983100e
3
+ metadata.gz: d3dec3fd3cab38f6a56c1fa13e3ee398e4b196c1c992cdf6f4c7e87c2c19e2f1
4
+ data.tar.gz: 9cd52e4ae25d980de2b8b4ba1988bbe42dac0dd686d0d1c7f23584c7889cb3e8
5
5
  SHA512:
6
- metadata.gz: 3103e356f667e9256ff3115d0381f05fa27906083232300c51833fcd5b15118fd4b4154542f07e781882a25a4de8f70b4604c8748c529f92a91d41af315391c4
7
- data.tar.gz: 965406b44c6006b4ac863cc2dd3d8fc0872fd87a08bb4fadc45954cd81e6a724f7143975985c439a83a4a9a9d0630df54113a2e9e018cd16282ea420eff23315
6
+ metadata.gz: 958f1f99429e00465cdc9a562dff1717da0b145827430704766756626444559e25f1f0b615c7f19cb274bc1783e33fb2eb3ca34b8e4d114e08af5f01f75c05f2
7
+ data.tar.gz: 0cfb7dc00f8eacff31bff96cfedee003427e9e9e4677f8a382f7aa28e0148de5ca902ca576697aeba54c3b28a19a3302f451fb0944dc96464908742a9328287f
data/CHANGELOG.md CHANGED
@@ -1,4 +1,7 @@
1
1
  # OrderOptimizer Gem Changelog
2
2
 
3
+ ## 0.2.0 (2019-12-16)
4
+
5
+ - Add support for discounts when ordering a minimum quantity
3
6
 
4
7
  ## 0.1.0 First release
data/README.md CHANGED
@@ -11,7 +11,7 @@ Imagine a product can be ordered in different pack sizes and each pack size has
11
11
  | 10 | 6.00 |
12
12
  | 1 | 9.00 |
13
13
 
14
- In this example, it is quite obvious that it is cheaper buying one 10-pack instead of nine 1-packs when you need 9 units (because the 10-pack costs 60.00 but nine 1-packs would cost 81.00).
14
+ In this example, it is quite obvious that it is cheaper buying one 10-pack instead of nine 1-packs when you need 9 units (because the 10-pack costs 60.00 but nine 1-packs would cost 81.00).
15
15
 
16
16
  But what would be the cheapest combination when you need 946 units? Or 947?
17
17
 
@@ -56,6 +56,34 @@ cheapest_order_for_947_units.total # => 3_900.00
56
56
  cheapest_order_for_947_units.skus # => { '1000-pack' => 1 }
57
57
  ```
58
58
 
59
+ It is possible to define dicount prices a minimum quantity:
60
+
61
+ ```ruby
62
+ order_optimizer = OrderOptimizer.new(
63
+ '1-pack' => { quantity: 1, price_per_unit: 9 },
64
+ '10-discount' => { quantity: 1, min_quantity: 10, price_per_unit: 8 },
65
+ '20-pack' => { quantity: 20, price_per_unit: 7 }
66
+ )
67
+
68
+ order_optimizer.cheapest_order(required_qty: 8).skus
69
+ #=> { '1-pack' => 8 }
70
+
71
+ order_optimizer.cheapest_order(required_qty: 9).skus
72
+ #=> { '10-discount' => 10 }
73
+
74
+ order_optimizer.cheapest_order(required_qty: 17).skus
75
+ #=> { '10-discount' => 17 }
76
+
77
+ order_optimizer.cheapest_order(required_qty: 18).skus
78
+ #=> { '20-pack' => 1 }
79
+
80
+ order_optimizer.cheapest_order(required_qty: 21).skus
81
+ #=> { '20-pack' => 1, '1-pack' => 1 }
82
+
83
+ order_optimizer.cheapest_order(required_qty: 29).skus
84
+ #=> { '20-pack' => 1, '10-discount' => 10 }
85
+ ```
86
+
59
87
  ## Development
60
88
 
61
89
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -9,5 +9,13 @@ 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
12
20
  end
13
21
  end
@@ -1,12 +1,25 @@
1
1
  class OrderOptimizer
2
2
  class Sku
3
- attr_reader :id, :quantity, :price_per_unit, :price_per_sku
3
+ attr_reader :id, :quantity, :price_per_unit, :price_per_sku, :min_quantity
4
4
 
5
- def initialize(id, quantity:, price_per_unit:)
5
+ def initialize(id, quantity:, price_per_unit:, min_quantity: nil)
6
6
  @id = id
7
7
  @quantity = quantity
8
8
  @price_per_unit = BigDecimal(price_per_unit, 2)
9
9
  @price_per_sku = quantity * price_per_unit
10
+ @min_quantity = min_quantity
11
+ end
12
+
13
+ private
14
+
15
+ def set_min_quantity(min_qty)
16
+ return unless min_qty
17
+
18
+ if min_quantity.modulo(quantity).zero?
19
+ @min_quantity = min_qty
20
+ else
21
+ raise ':min_quantity must be a multiple of :quantity'
22
+ end
10
23
  end
11
24
  end
12
25
  end
@@ -1,3 +1,3 @@
1
1
  class OrderOptimizer
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -10,21 +10,39 @@ class OrderOptimizer
10
10
  end
11
11
 
12
12
  def cheapest_order(required_qty:)
13
- possible_orders(required_qty: required_qty)
14
- .min_by(&:total)
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
15
19
  end
16
20
 
17
21
  private
18
22
 
19
- def possible_orders(required_qty:)
23
+ def possible_orders(required_qty:, skus:)
20
24
  [].tap do |orders|
21
- skus = @catalog.skus
22
25
  order = OrderOptimizer::Order.new
23
- while sku = skus.shift
26
+
27
+ while required_qty.positive? && (sku = skus.shift)
24
28
  count, remainder = (required_qty - order.quantity).divmod(sku.quantity)
29
+ count, remainder = adjustment_for_skus_with_min_quantity(sku, count, remainder)
30
+
25
31
  order.add(sku, count: count) unless count.zero?
26
- orders << (remainder.zero? ? order.dup : order.dup.add(sku))
32
+
33
+ orders << (remainder.positive? ? order.dup.add(sku) : order.dup)
34
+
35
+ order = OrderOptimizer::Order.new if sku.min_quantity
27
36
  end
28
37
  end
29
38
  end
39
+
40
+ def adjustment_for_skus_with_min_quantity(sku, count, remainder)
41
+ units = count * sku.quantity
42
+
43
+ return count, remainder if sku.min_quantity.nil? || units >= sku.min_quantity
44
+
45
+ delta = ((sku.min_quantity - units).to_f / sku.quantity).ceil
46
+ [count + delta, remainder - (delta * sku.quantity)]
47
+ end
30
48
  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.1.0
4
+ version: 0.2.0
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-13 00:00:00.000000000 Z
12
+ date: 2019-12-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler