order_optimizer 0.3.0 → 0.5.1

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: f7582fc4c48f45ba73759ff312cc21a0bfe7731a5b109eafcb313c2c0930d435
4
- data.tar.gz: b10e675e41325916aede4dae3cbbc2f3dddc075bd0f0164daa5f61948c813c9c
3
+ metadata.gz: dc5f8c71f67ec7c63ccf96363c1653637f5f4ea50d866485cdd4f6238594522d
4
+ data.tar.gz: 13c69c75805106de2e1ae6a0031bb86639f05c435c49732aaae3124375458efb
5
5
  SHA512:
6
- metadata.gz: 3b92cd98cbb49a93f5dd152673fca3e44d8ff531a6eae3fd87e1bd44c0bfeaebc02e580c69694da21069450dfae897a0ed641e2fe217b18a93a3d1ead28c4efd
7
- data.tar.gz: 1bb4af7ddd21d6bb5ddf22b18ed0b0e099609de555c3350837a84478b516a1f43adf2c0a58e818ca17abd443ce254dc862596661d77f8b5d46937d8f9f5b1a05
6
+ metadata.gz: '0633497552abad8d67891a0f2952fafa9221ba4941709440c0e515f19f8241fb05bd30ccf4129b1c4a879686ef9203d75565c37556e78fdc0b73db0bca862085'
7
+ data.tar.gz: 03742d3da4bd0db3beb4b19c824f99cc17db427a84c63e27944bd0d20327eff779cfdb355fb0613ab6179c9950279b5427e247bc8802517955a326d523b992e1
@@ -0,0 +1,63 @@
1
+ version: 2.1
2
+ jobs:
3
+ test:
4
+ docker:
5
+ - image: circleci/ruby:3.0
6
+
7
+ working_directory: ~/repo
8
+
9
+ steps:
10
+ - checkout
11
+
12
+ - restore_cache:
13
+ keys:
14
+ - v1-dependencies-{{ checksum "order_optimizer.gemspec" }}
15
+ # fallback to using the latest cache if no exact match is found
16
+ - v1-dependencies-
17
+
18
+ - run:
19
+ name: install dependencies
20
+ command: bundle install --jobs=4 --retry=3 --path vendor/bundle
21
+
22
+ - save_cache:
23
+ paths:
24
+ - ./vendor/bundle
25
+ key: v1-dependencies-{{ checksum "order_optimizer.gemspec" }}
26
+
27
+ - run:
28
+ name: run tests
29
+ command: bundle exec rake
30
+
31
+ publish:
32
+ docker:
33
+ - image: circleci/ruby:3.0
34
+ working_directory: ~/repo
35
+ steps:
36
+ - checkout
37
+ - run:
38
+ name: Build package
39
+ command: gem build order_optimizer.gemspec
40
+ - run:
41
+ name: Push package
42
+ command: |
43
+ VERSION=$(ruby -r "./lib/order_optimizer/version.rb" -e "print OrderOptimizer::VERSION")
44
+ gem push order_optimizer-${VERSION}.gem
45
+
46
+ workflows:
47
+ default:
48
+ jobs:
49
+ - test:
50
+ filters:
51
+ tags:
52
+ only: /.*/
53
+ branches:
54
+ only: /.*/
55
+ - publish:
56
+ context:
57
+ - rubygems-push
58
+ requires: [test]
59
+ filters:
60
+ tags:
61
+ only: /^v.*/
62
+ branches:
63
+ ignore: /.*/
data/.gitignore CHANGED
@@ -6,3 +6,4 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
+ Gemfile.lock
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # OrderOptimizer Gem Changelog
2
2
 
3
+ ## 0.5.1 (2021-11-29)
4
+ - Consider `max_quantity` when desired exceeds OR EQUALS max_quantity
5
+
6
+ ## 0.5.0 (2021-04-15)
7
+ - Add `maximum_quantity` for SKUs
8
+
9
+ ## 0.4.1 (2021-02-15)
10
+
11
+ - Fix issue with floating point conversion [#3]
12
+
13
+ ## 0.4.0 (2020-12-11)
14
+
15
+ - Add `possible_orders` method to find all possible orders
16
+ - Fix issues with zero remainders on calculations
17
+
3
18
  ## 0.3.0 (2020-11-23)
4
19
 
5
20
  - Add `cheapest_exact_order` method to optimize an order with an exact amount
@@ -13,3 +28,5 @@
13
28
  - Add support for discounts when ordering a minimum quantity
14
29
 
15
30
  ## 0.1.0 First release
31
+
32
+ [#3]: https://github.com/zaikio/order_optimizer/pull/3
data/README.md CHANGED
@@ -72,7 +72,7 @@ cheapest_exact_order_for_58_units.total # => 322
72
72
  cheapest_exact_order_for_58_units.skus # => { '10-pack' => 5, '1-pack' => 8 }
73
73
  ```
74
74
 
75
- It is possible to define dicount prices a minimum quantity:
75
+ It is possible to define discount prices with a minimum quantity:
76
76
 
77
77
  ```ruby
78
78
  order_optimizer = OrderOptimizer.new(
@@ -100,15 +100,52 @@ order_optimizer.cheapest_order(required_qty: 29).skus
100
100
  #=> { '20-pack' => 1, '10-discount' => 10 }
101
101
  ```
102
102
 
103
+ It is also possible to define discount prices with a maximum quantity:
104
+ ```ruby
105
+ order_optimizer = OrderOptimizer.new(
106
+ '1-pack' => { quantity: 1, price_per_unit: 9 },
107
+ '10-pack' => { quantity: 10, price_per_unit: 8, max_quantity: 20 },
108
+ )
109
+
110
+ order_optimizer.cheapest_order(required_qty: 30).skus
111
+ #=> { '10-pack' => 2, '1-pack' => 10 }
112
+ ```
113
+
114
+ If you're just interested in all possible order combinations:
115
+
116
+ ```ruby
117
+ # Initialize the optimize with a catalog
118
+ order_optimizer = OrderOptimizer.new(
119
+ '1-pack' => { quantity: 1, price_per_unit: 9 },
120
+ '10-pack' => { quantity: 10, price_per_unit: 5 },
121
+ )
122
+
123
+ order_optimizer.possible_orders(required_qty: 11).size
124
+ #=> 3
125
+ order_optimizer.possible_orders(required_qty: 10).map(&:skus)
126
+ #=> [{"10-pack"=>1, "1-pack"=>1}, {"1-pack"=>11}, {"10-pack"=>2}]
127
+ ```
128
+
103
129
  ## Development
104
130
 
105
131
  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.
106
132
 
107
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
108
-
109
133
  ## Contributing
110
134
 
111
- Bug reports and pull requests are welcome on GitHub at https://github.com/crispymtn/order_optimizer. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
135
+ Bug reports and pull requests are welcome on GitHub at
136
+ https://github.com/zaikio/order_optimizer. This project is intended to be a safe,
137
+ welcoming space for collaboration, and contributors are expected to adhere to the
138
+ [Contributor Covenant](http://contributor-covenant.org) code of conduct.
139
+
140
+ - Make your changes and submit a pull request for them
141
+ - Make sure to update `CHANGELOG.md`
142
+
143
+ To release a new version of the gem:
144
+ - Update the version in `lib/order_optimizer/version.rb`
145
+ - Update `CHANGELOG.md` to include the new version and its release date
146
+ - Commit and push your changes
147
+ - Create a [new release on GitHub](https://github.com/zaikio/order_optimizer/releases/new)
148
+ - CircleCI will build the Gem package and push it Rubygems for you
112
149
 
113
150
  ## License
114
151
 
@@ -116,4 +153,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
116
153
 
117
154
  ## Code of Conduct
118
155
 
119
- Everyone interacting in the OrderOptimizer project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/crispymtn/order_optimizer/blob/master/CODE_OF_CONDUCT.md).
156
+ Everyone interacting in the OrderOptimizer project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/zaikio/order_optimizer/blob/master/CODE_OF_CONDUCT.md).
@@ -1,21 +1,24 @@
1
1
  class OrderOptimizer
2
2
  class Sku
3
- attr_reader :id, :quantity, :price_per_unit, :price_per_sku, :min_quantity
3
+ attr_reader :id, :quantity, :price_per_unit, :price_per_sku, :min_quantity, :max_quantity
4
4
 
5
- def initialize(id, quantity:, price_per_sku: nil, price_per_unit: nil, min_quantity: nil)
5
+ def initialize(id, quantity:, price_per_sku: nil, price_per_unit: nil, min_quantity: nil, max_quantity: nil)
6
6
  @id = id
7
- @quantity = quantity
8
- @min_quantity = min_quantity
7
+ @quantity = BigDecimal(quantity, 2)
8
+ @min_quantity = BigDecimal(min_quantity, 2) if min_quantity
9
+ @max_quantity = BigDecimal(max_quantity, 2) if max_quantity
9
10
 
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
12
-
13
- unless price_per_unit || price_per_sku
14
- raise ':price_per_sku or :price_per_unit must be set'
11
+ if min_quantity && max_quantity && (min_quantity > max_quantity)
12
+ raise ArgumentError, "min_quantity can't be larger than max_quantity"
15
13
  end
16
14
 
17
- @price_per_unit ||= price_per_sku.to_f / quantity
18
- @price_per_sku ||= quantity * price_per_unit
15
+ raise ArgumentError, ':price_per_sku or :price_per_unit must be set' unless price_per_unit || price_per_sku
16
+
17
+ @price_per_unit = BigDecimal(price_per_unit, 2) if price_per_unit
18
+ @price_per_unit ||= BigDecimal(price_per_sku, 2) / quantity
19
+
20
+ @price_per_sku = BigDecimal(price_per_sku, 2) if price_per_sku
21
+ @price_per_sku ||= quantity * price_per_unit
19
22
  end
20
23
  end
21
24
  end
@@ -1,3 +1,3 @@
1
1
  class OrderOptimizer
2
- VERSION = "0.3.0"
2
+ VERSION = "0.5.1".freeze
3
3
  end
@@ -10,37 +10,41 @@ class OrderOptimizer
10
10
  end
11
11
 
12
12
  def cheapest_order(required_qty:)
13
- possible_orders(skus: @catalog.skus, required_qty: required_qty).min_by(&:total) ||
13
+ find_possible_orders(skus: @catalog.skus, required_qty: required_qty).min_by(&:total) ||
14
14
  OrderOptimizer::Order.new(required_qty: required_qty)
15
15
  end
16
16
 
17
17
  def cheapest_exact_order(required_qty:)
18
- possible_orders(skus: @catalog.skus, required_qty: required_qty).select(&:exact?).min_by(&:total) ||
18
+ find_possible_orders(skus: @catalog.skus, required_qty: required_qty).select(&:exact?).min_by(&:total) ||
19
19
  OrderOptimizer::Order.new(required_qty: required_qty)
20
20
  end
21
21
 
22
+ def possible_orders(required_qty:)
23
+ find_possible_orders(skus: @catalog.skus, required_qty: required_qty).sort_by(&:total)
24
+ end
25
+
22
26
  private
23
27
 
24
- def possible_orders(required_qty:, skus:)
28
+ def find_possible_orders(required_qty:, skus:)
25
29
  return [] if required_qty < 1 || skus.empty?
26
30
 
27
31
  orders = []
28
32
 
29
33
  skus.each do |sku|
30
34
  orders.reject(&:complete?).each do |order|
31
- count, remainder = count_and_remainder_for_sku(order.missing_qty, sku)
35
+ count, remainder, skip_increase = count_and_remainder_for_sku(order.missing_qty, sku)
32
36
 
33
37
  orders << order.dup.add(sku, count: count) unless count.zero?
34
- orders << order.dup.add(sku, count: count + 1) if remainder
38
+ orders << order.dup.add(sku, count: count + 1) unless remainder.zero? || skip_increase
35
39
  end
36
40
 
37
- count, remainder = count_and_remainder_for_sku(required_qty, sku)
41
+ count, remainder, skip_increase = count_and_remainder_for_sku(required_qty, sku)
38
42
 
39
43
  unless count.zero?
40
44
  orders << OrderOptimizer::Order.new(required_qty: required_qty).add(sku, count: count)
41
45
  end
42
46
 
43
- if remainder
47
+ unless remainder.zero? || skip_increase
44
48
  orders << OrderOptimizer::Order.new(required_qty: required_qty).add(sku, count: count + 1)
45
49
  end
46
50
  end
@@ -51,12 +55,18 @@ class OrderOptimizer
51
55
  def count_and_remainder_for_sku(quantity, sku)
52
56
  count, remainder = quantity.divmod(sku.quantity)
53
57
 
54
- if sku.min_quantity && count * sku.quantity < sku.min_quantity
55
- new_count = (sku.min_quantity.to_f / sku.quantity).ceil
58
+ if sku.max_quantity && count * sku.quantity >= sku.max_quantity
59
+ new_count = (sku.max_quantity / sku.quantity).ceil
60
+ remainder = (count - new_count) * sku.quantity
61
+
62
+ [new_count, [remainder, 0].max, true]
63
+ elsif sku.min_quantity && count * sku.quantity < sku.min_quantity
64
+ new_count = (sku.min_quantity / sku.quantity).ceil
56
65
  remainder -= (new_count - count) * sku.quantity
57
- [new_count, [remainder, 0].max]
66
+
67
+ [new_count, [remainder, 0].max, false]
58
68
  else
59
- [count, remainder]
69
+ [count, remainder, false]
60
70
  end
61
71
  end
62
72
  end
@@ -7,13 +7,13 @@ Gem::Specification.new do |spec|
7
7
  spec.version = OrderOptimizer::VERSION
8
8
 
9
9
  spec.authors = ["crispymtn", "Martin Spickermann", "Maurice Vogel"]
10
- spec.email = ["op@crispymtn.com", "spickermann@gmail.com"]
11
- spec.homepage = "https://github.com/crispymtn/order_optimizer"
10
+ spec.email = ["op@zaikio.com", "spickermann@gmail.com"]
11
+ spec.homepage = "https://github.com/zaikio/order_optimizer"
12
12
  spec.license = "MIT"
13
13
  spec.summary = "Helps to optimize orders if the goods are offered in different pack sizes and in different discount levels."
14
14
 
15
15
 
16
- spec.metadata["changelog_uri"] = "https://github.com/crispymtn/order_optimizer/blob/master/CHANGELOG.md"
16
+ spec.metadata["changelog_uri"] = "https://github.com/zaikio/order_optimizer/blob/master/CHANGELOG.md"
17
17
  spec.metadata["homepage_uri"] = spec.homepage
18
18
  spec.metadata["source_code_uri"] = spec.homepage
19
19
 
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.3.0
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - crispymtn
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2020-11-23 00:00:00.000000000 Z
13
+ date: 2021-11-29 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: bundler
@@ -56,18 +56,18 @@ dependencies:
56
56
  version: '0'
57
57
  description:
58
58
  email:
59
- - op@crispymtn.com
59
+ - op@zaikio.com
60
60
  - spickermann@gmail.com
61
61
  executables: []
62
62
  extensions: []
63
63
  extra_rdoc_files: []
64
64
  files:
65
+ - ".circleci/config.yml"
65
66
  - ".gitignore"
66
67
  - ".travis.yml"
67
68
  - CHANGELOG.md
68
69
  - CODE_OF_CONDUCT.md
69
70
  - Gemfile
70
- - Gemfile.lock
71
71
  - LICENSE.txt
72
72
  - README.md
73
73
  - Rakefile
@@ -79,13 +79,13 @@ files:
79
79
  - lib/order_optimizer/sku.rb
80
80
  - lib/order_optimizer/version.rb
81
81
  - order_optimizer.gemspec
82
- homepage: https://github.com/crispymtn/order_optimizer
82
+ homepage: https://github.com/zaikio/order_optimizer
83
83
  licenses:
84
84
  - MIT
85
85
  metadata:
86
- changelog_uri: https://github.com/crispymtn/order_optimizer/blob/master/CHANGELOG.md
87
- homepage_uri: https://github.com/crispymtn/order_optimizer
88
- source_code_uri: https://github.com/crispymtn/order_optimizer
86
+ changelog_uri: https://github.com/zaikio/order_optimizer/blob/master/CHANGELOG.md
87
+ homepage_uri: https://github.com/zaikio/order_optimizer
88
+ source_code_uri: https://github.com/zaikio/order_optimizer
89
89
  post_install_message:
90
90
  rdoc_options: []
91
91
  require_paths:
@@ -101,7 +101,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
101
101
  - !ruby/object:Gem::Version
102
102
  version: '0'
103
103
  requirements: []
104
- rubygems_version: 3.1.2
104
+ rubygems_version: 3.2.22
105
105
  signing_key:
106
106
  specification_version: 4
107
107
  summary: Helps to optimize orders if the goods are offered in different pack sizes
data/Gemfile.lock DELETED
@@ -1,22 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- order_optimizer (0.3.0)
5
-
6
- GEM
7
- remote: https://rubygems.org/
8
- specs:
9
- minitest (5.13.0)
10
- rake (13.0.1)
11
-
12
- PLATFORMS
13
- ruby
14
-
15
- DEPENDENCIES
16
- bundler
17
- minitest
18
- order_optimizer!
19
- rake
20
-
21
- BUNDLED WITH
22
- 2.1.4